@frontmcp/sdk 1.8.3 → 1.8.4

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 (104) hide show
  1. package/agent/agent-execution-loop.d.ts.map +1 -1
  2. package/agent/agent.instance.d.ts +6 -1
  3. package/agent/agent.instance.d.ts.map +1 -1
  4. package/auth/flows/auth.verify.flow.d.ts.map +1 -1
  5. package/auth/flows/oauth.authorize.flow.d.ts +20 -11
  6. package/auth/flows/oauth.authorize.flow.d.ts.map +1 -1
  7. package/auth/flows/oauth.callback.flow.d.ts +3 -0
  8. package/auth/flows/oauth.callback.flow.d.ts.map +1 -1
  9. package/auth/flows/oauth.token.flow.d.ts.map +1 -1
  10. package/auth/flows/oauth.userinfo.flow.d.ts.map +1 -1
  11. package/auth/flows/session.verify.flow.d.ts.map +1 -1
  12. package/auth/flows/well-known.oauth-authorization-server.flow.d.ts.map +1 -1
  13. package/auth/flows/well-known.prm.flow.d.ts.map +1 -1
  14. package/auth/instances/instance.local-primary-auth.d.ts +37 -6
  15. package/auth/instances/instance.local-primary-auth.d.ts.map +1 -1
  16. package/auth/secure-store/secure-store.providers.d.ts +8 -5
  17. package/auth/secure-store/secure-store.providers.d.ts.map +1 -1
  18. package/auth/session-scope-identity.d.ts +18 -0
  19. package/auth/session-scope-identity.d.ts.map +1 -0
  20. package/auth/vault/auth-providers.providers.d.ts +2 -4
  21. package/auth/vault/auth-providers.providers.d.ts.map +1 -1
  22. package/channel/sources/webhook.source.d.ts +1 -1
  23. package/channel/sources/webhook.source.d.ts.map +1 -1
  24. package/common/availability/availability.utils.d.ts +4 -2
  25. package/common/availability/availability.utils.d.ts.map +1 -1
  26. package/common/decorators/hook.decorator.d.ts +2 -0
  27. package/common/decorators/hook.decorator.d.ts.map +1 -1
  28. package/common/interfaces/agent.interface.d.ts +8 -0
  29. package/common/interfaces/agent.interface.d.ts.map +1 -1
  30. package/common/interfaces/channel.interface.d.ts +6 -0
  31. package/common/interfaces/channel.interface.d.ts.map +1 -1
  32. package/common/interfaces/execution-context.interface.d.ts +12 -0
  33. package/common/interfaces/execution-context.interface.d.ts.map +1 -1
  34. package/common/interfaces/internal/primary-auth-provider.interface.d.ts +5 -1
  35. package/common/interfaces/internal/primary-auth-provider.interface.d.ts.map +1 -1
  36. package/common/interfaces/job.interface.d.ts +3 -0
  37. package/common/interfaces/job.interface.d.ts.map +1 -1
  38. package/common/interfaces/server.interface.d.ts +2 -0
  39. package/common/interfaces/server.interface.d.ts.map +1 -1
  40. package/common/metadata/plugin.metadata.d.ts +9 -0
  41. package/common/metadata/plugin.metadata.d.ts.map +1 -1
  42. package/common/tokens/plugin.tokens.d.ts +1 -0
  43. package/common/tokens/plugin.tokens.d.ts.map +1 -1
  44. package/common/utils/auth-info.utils.d.ts +26 -1
  45. package/common/utils/auth-info.utils.d.ts.map +1 -1
  46. package/common/utils/guard.utils.d.ts +2 -0
  47. package/common/utils/guard.utils.d.ts.map +1 -1
  48. package/context/call-surface.d.ts +6 -4
  49. package/context/call-surface.d.ts.map +1 -1
  50. package/context/frontmcp-context.d.ts +9 -1
  51. package/context/frontmcp-context.d.ts.map +1 -1
  52. package/direct/direct-server.d.ts.map +1 -1
  53. package/elicitation/helpers/elicit.helper.d.ts.map +1 -1
  54. package/elicitation/send-elicitation-result.tool.d.ts.map +1 -1
  55. package/errors/error-handler.d.ts.map +1 -1
  56. package/errors/index.d.ts +1 -0
  57. package/errors/index.d.ts.map +1 -1
  58. package/errors/mcp.error.d.ts.map +1 -1
  59. package/errors/session-identity-required.error.d.ts +20 -0
  60. package/errors/session-identity-required.error.d.ts.map +1 -0
  61. package/esm/index.mjs +873 -223
  62. package/flows/flow.http-path.d.ts +21 -0
  63. package/flows/flow.http-path.d.ts.map +1 -0
  64. package/flows/flow.instance.d.ts.map +1 -1
  65. package/front-mcp/front-mcp.d.ts +5 -0
  66. package/front-mcp/front-mcp.d.ts.map +1 -1
  67. package/front-mcp/static-startup.check.d.ts +12 -0
  68. package/front-mcp/static-startup.check.d.ts.map +1 -0
  69. package/hooks/hook-coverage.d.ts +29 -0
  70. package/hooks/hook-coverage.d.ts.map +1 -0
  71. package/index.d.ts +9 -5
  72. package/index.d.ts.map +1 -1
  73. package/index.js +1366 -715
  74. package/job/execution/job-execution.manager.d.ts.map +1 -1
  75. package/notification/notification.service.d.ts.map +1 -1
  76. package/package.json +10 -10
  77. package/prompt/flows/get-prompt.flow.d.ts.map +1 -1
  78. package/scope/flows/http.request.flow.d.ts +25 -1
  79. package/scope/flows/http.request.flow.d.ts.map +1 -1
  80. package/scope/scope.instance.d.ts.map +1 -1
  81. package/skill/sep-2640/sep-2640.per-skill.d.ts.map +1 -1
  82. package/skill/skill-filter.helper.d.ts +6 -2
  83. package/skill/skill-filter.helper.d.ts.map +1 -1
  84. package/skill/skill-scope.helper.d.ts +11 -1
  85. package/skill/skill-scope.helper.d.ts.map +1 -1
  86. package/skill/skill.registry.d.ts +27 -0
  87. package/skill/skill.registry.d.ts.map +1 -1
  88. package/task/helpers/task-runner.d.ts.map +1 -1
  89. package/task/runtime/execute-task.d.ts.map +1 -1
  90. package/task/task.types.d.ts +7 -0
  91. package/task/task.types.d.ts.map +1 -1
  92. package/tool/flows/call-tool.flow.d.ts.map +1 -1
  93. package/transport/flows/handle.mcp-20260728.flow.d.ts.map +1 -1
  94. package/transport/flows/handle.streamable-http.flow.d.ts.map +1 -1
  95. package/transport/in-memory-server.d.ts.map +1 -1
  96. package/transport/index.d.ts +1 -0
  97. package/transport/index.d.ts.map +1 -1
  98. package/transport/mcp-handlers/index.d.ts.map +1 -1
  99. package/transport/persistent-session-owner.d.ts +41 -0
  100. package/transport/persistent-session-owner.d.ts.map +1 -0
  101. package/transport/web-fetch-handler.d.ts.map +1 -1
  102. package/transport/web-standard-mcp.d.ts +4 -0
  103. package/transport/web-standard-mcp.d.ts.map +1 -1
  104. package/workflow/engine/workflow-step.executor.d.ts.map +1 -1
package/esm/index.mjs CHANGED
@@ -110,7 +110,7 @@ function extractPublicMessageImpl(error, visited) {
110
110
  }
111
111
  return error == null ? "Unknown error" : String(error);
112
112
  }
113
- var MCP_ERROR_CODES, McpError, PublicMcpError, InternalMcpError, ToolNotFoundError, ToolNotConsentedError, EntryUnavailableError, ResourceNotFoundError, ResourceReadError, InvalidResourceUriError, InvalidInputError, InvalidOutputError, InvalidMethodError, PayloadTooLargeError, ToolExecutionError, RateLimitError, QuotaExceededError, UnauthorizedError, GenericServerError, DependencyNotFoundError, InvalidHookFlowError, InvalidPluginScopeError, RequestContextNotAvailableError, AuthConfigurationError, PromptNotFoundError, PromptExecutionError, SessionMissingError, UnsupportedClientVersionError, GlobalConfigNotFoundError, AuthorityDeniedMcpError, GuardLimitMcpError;
113
+ var MCP_ERROR_CODES, sequentialErrorIds, McpError, PublicMcpError, InternalMcpError, ToolNotFoundError, ToolNotConsentedError, EntryUnavailableError, ResourceNotFoundError, ResourceReadError, InvalidResourceUriError, InvalidInputError, InvalidOutputError, InvalidMethodError, PayloadTooLargeError, ToolExecutionError, RateLimitError, QuotaExceededError, UnauthorizedError, GenericServerError, DependencyNotFoundError, InvalidHookFlowError, InvalidPluginScopeError, RequestContextNotAvailableError, AuthConfigurationError, PromptNotFoundError, PromptExecutionError, SessionMissingError, UnsupportedClientVersionError, GlobalConfigNotFoundError, AuthorityDeniedMcpError, GuardLimitMcpError;
114
114
  var init_mcp_error = __esm({
115
115
  "libs/sdk/src/errors/mcp.error.ts"() {
116
116
  "use strict";
@@ -132,6 +132,7 @@ var init_mcp_error = __esm({
132
132
  /** Parse error (-32700) */
133
133
  PARSE_ERROR: -32700
134
134
  };
135
+ sequentialErrorIds = 0;
135
136
  McpError = class extends Error {
136
137
  /**
137
138
  * Unique error ID for tracking in logs
@@ -144,7 +145,12 @@ var init_mcp_error = __esm({
144
145
  Error.captureStackTrace(this, this.constructor);
145
146
  }
146
147
  generateErrorId() {
147
- return `err_${bytesToHex(randomBytes(8))}`;
148
+ try {
149
+ return `err_${bytesToHex(randomBytes(8))}`;
150
+ } catch {
151
+ sequentialErrorIds += 1;
152
+ return `err_${sequentialErrorIds.toString(16).padStart(16, "0")}`;
153
+ }
148
154
  }
149
155
  /**
150
156
  * Get the internal error message (for logging)
@@ -865,7 +871,8 @@ var init_plugin_tokens = __esm({
865
871
  skills: tokenFactory.meta("skills"),
866
872
  scope: tokenFactory.meta("scope"),
867
873
  contextExtensions: tokenFactory.meta("contextExtensions"),
868
- enforcesMetadata: tokenFactory.meta("enforcesMetadata")
874
+ enforcesMetadata: tokenFactory.meta("enforcesMetadata"),
875
+ dynamicSkills: tokenFactory.meta("dynamicSkills")
869
876
  };
870
877
  }
871
878
  });
@@ -3436,7 +3443,8 @@ var init_plugin_metadata = __esm({
3436
3443
  skills: z30.array(annotatedFrontMcpSkillsSchema).optional(),
3437
3444
  scope: z30.enum(["app", "server"]).optional().default("app"),
3438
3445
  contextExtensions: z30.array(contextExtensionSchema).optional(),
3439
- enforcesMetadata: z30.array(z30.string().min(1)).optional()
3446
+ enforcesMetadata: z30.array(z30.string().min(1)).optional(),
3447
+ dynamicSkills: z30.boolean().optional()
3440
3448
  }).passthrough();
3441
3449
  }
3442
3450
  });
@@ -4571,6 +4579,25 @@ var init_tool_credentials_required_error = __esm({
4571
4579
  }
4572
4580
  });
4573
4581
 
4582
+ // libs/sdk/src/errors/session-identity-required.error.ts
4583
+ var SessionIdentityRequiredError;
4584
+ var init_session_identity_required_error = __esm({
4585
+ "libs/sdk/src/errors/session-identity-required.error.ts"() {
4586
+ "use strict";
4587
+ init_mcp_error();
4588
+ SessionIdentityRequiredError = class extends PublicMcpError {
4589
+ mcpErrorCode = MCP_ERROR_CODES.FORBIDDEN;
4590
+ constructor(what = "Session-scoped storage") {
4591
+ super(
4592
+ `${what} needs a verified session or a signed-in caller. Authenticate the request, or use a session transport (an MCP revision before 2026-07-28).`,
4593
+ "SESSION_IDENTITY_REQUIRED",
4594
+ 403
4595
+ );
4596
+ }
4597
+ };
4598
+ }
4599
+ });
4600
+
4574
4601
  // libs/sdk/src/errors/error-handler.ts
4575
4602
  import { isProduction as isProduction2 } from "@frontmcp/utils";
4576
4603
  function createErrorHandler(options = {}) {
@@ -5932,6 +5959,7 @@ __export(errors_exports, {
5932
5959
  ServerlessHandlerNotInitializedError: () => ServerlessHandlerNotInitializedError,
5933
5960
  SessionClaimConflictError: () => SessionClaimConflictError,
5934
5961
  SessionIdEmptyError: () => SessionIdEmptyError,
5962
+ SessionIdentityRequiredError: () => SessionIdentityRequiredError,
5935
5963
  SessionMissingError: () => SessionMissingError,
5936
5964
  SessionSecretRequiredError: () => SessionSecretRequiredError,
5937
5965
  SessionVerificationFailedError: () => SessionVerificationFailedError,
@@ -5986,6 +6014,7 @@ var init_errors = __esm({
5986
6014
  init_mcp_error();
5987
6015
  init_authorization_required_error();
5988
6016
  init_tool_credentials_required_error();
6017
+ init_session_identity_required_error();
5989
6018
  init_error_handler();
5990
6019
  init_agent_errors();
5991
6020
  init_task_error();
@@ -6468,30 +6497,30 @@ var require_dist = __commonJS({
6468
6497
  sqliteStorageOptionsSchema: () => sqliteStorageOptionsSchema
6469
6498
  });
6470
6499
  module.exports = __toCommonJS2(index_exports);
6471
- var import_utils108 = __require("@frontmcp/utils");
6500
+ var import_utils113 = __require("@frontmcp/utils");
6472
6501
  var HKDF_SALT = new TextEncoder().encode("frontmcp-sqlite-storage-v1");
6473
6502
  var HKDF_INFO = new TextEncoder().encode("aes-256-gcm-value-encryption");
6474
6503
  var KEY_LENGTH = 32;
6475
6504
  function deriveEncryptionKey(secret) {
6476
6505
  const ikm = new TextEncoder().encode(secret);
6477
- return (0, import_utils108.hkdfSha256)(ikm, HKDF_SALT, HKDF_INFO, KEY_LENGTH);
6506
+ return (0, import_utils113.hkdfSha256)(ikm, HKDF_SALT, HKDF_INFO, KEY_LENGTH);
6478
6507
  }
6479
6508
  var SEPARATOR = ":";
6480
6509
  function encryptValue(key, plaintext) {
6481
- const iv = (0, import_utils108.randomBytes)(12);
6510
+ const iv = (0, import_utils113.randomBytes)(12);
6482
6511
  const plaintextBytes = new TextEncoder().encode(plaintext);
6483
- const { ciphertext, tag } = (0, import_utils108.encryptAesGcm)(key, plaintextBytes, iv);
6484
- return [(0, import_utils108.base64urlEncode)(iv), (0, import_utils108.base64urlEncode)(tag), (0, import_utils108.base64urlEncode)(ciphertext)].join(SEPARATOR);
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);
6485
6514
  }
6486
6515
  function decryptValue(key, encrypted) {
6487
6516
  const parts = encrypted.split(SEPARATOR);
6488
6517
  if (parts.length !== 3) {
6489
6518
  throw new Error("Invalid encrypted value format");
6490
6519
  }
6491
- const iv = (0, import_utils108.base64urlDecode)(parts[0]);
6492
- const tag = (0, import_utils108.base64urlDecode)(parts[1]);
6493
- const ciphertext = (0, import_utils108.base64urlDecode)(parts[2]);
6494
- const plaintext = (0, import_utils108.decryptAesGcm)(key, ciphertext, iv, tag);
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);
6495
6524
  return new TextDecoder().decode(plaintext);
6496
6525
  }
6497
6526
  var import_utils210 = __require("@frontmcp/utils");
@@ -7405,8 +7434,8 @@ function detectStorageType(storage, configuredType) {
7405
7434
  return "memory";
7406
7435
  }
7407
7436
  async function createTaskStore(options = {}) {
7408
- const { storage: storageConfig, redis, keyPrefix = "mcp:task:", logger, isEdgeRuntime: isEdgeRuntime2 = false, sqlite } = options;
7409
- if (isEdgeRuntime2 && sqlite) {
7437
+ const { storage: storageConfig, redis, keyPrefix = "mcp:task:", logger, isEdgeRuntime: isEdgeRuntime3 = false, sqlite } = options;
7438
+ if (isEdgeRuntime3 && sqlite) {
7410
7439
  throw new TaskStoreNotSupportedError(
7411
7440
  "SQLite task store is not supported on Edge runtime (better-sqlite3 is a native module). Use Redis or Upstash."
7412
7441
  );
@@ -7461,7 +7490,7 @@ async function createTaskStore(options = {}) {
7461
7490
  "Vercel KV is not supported for task stores (pub/sub required). Use Redis or Upstash."
7462
7491
  );
7463
7492
  }
7464
- if (isEdgeRuntime2 && (!finalStorageConfig || finalStorageConfig.type === "memory")) {
7493
+ if (isEdgeRuntime3 && (!finalStorageConfig || finalStorageConfig.type === "memory")) {
7465
7494
  throw new TaskStoreNotSupportedError(
7466
7495
  "Tasks require distributed storage on Edge runtime. Configure Redis or Upstash."
7467
7496
  );
@@ -7469,7 +7498,7 @@ async function createTaskStore(options = {}) {
7469
7498
  const storage = await createStorage({
7470
7499
  ...finalStorageConfig,
7471
7500
  prefix: keyPrefix,
7472
- fallback: isEdgeRuntime2 ? "error" : "memory"
7501
+ fallback: isEdgeRuntime3 ? "error" : "memory"
7473
7502
  });
7474
7503
  const store = new StorageTaskStore(storage, logger);
7475
7504
  const type = detectStorageType(storage, finalStorageConfig?.type);
@@ -7651,8 +7680,9 @@ var init_task_id = __esm({
7651
7680
  });
7652
7681
 
7653
7682
  // libs/sdk/src/task/helpers/task-runner.ts
7683
+ import { runRequestExclusive } from "@frontmcp/utils";
7654
7684
  function runTaskInBackground(params) {
7655
- return Promise.resolve().then(() => executeTask(params));
7685
+ return Promise.resolve().then(() => runRequestExclusive(() => executeTask(params)));
7656
7686
  }
7657
7687
  async function executeTask(params) {
7658
7688
  const { record, cleanedRequestParams, ctx, scope, store, registry, notifier, logger } = params;
@@ -8694,6 +8724,7 @@ var init_get_prompt_request_handler = __esm({
8694
8724
  import {
8695
8725
  ListRootsResultSchema
8696
8726
  } from "@frontmcp/protocol";
8727
+ import { awaitOutsideRequest } from "@frontmcp/utils";
8697
8728
  function hasMcpAppsExtension(capabilities) {
8698
8729
  if (!capabilities?.experimental) {
8699
8730
  return false;
@@ -9410,9 +9441,11 @@ var init_notification_service = __esm({
9410
9441
  }
9411
9442
  try {
9412
9443
  this.logger.verbose(`Requesting roots from client for session ${sessionId.slice(0, 20)}...`);
9413
- const result = await registered.server.request({ method: "roots/list" }, ListRootsResultSchema, {
9414
- timeout: options?.timeout ?? 3e4
9415
- });
9444
+ const result = await awaitOutsideRequest(
9445
+ registered.server.request({ method: "roots/list" }, ListRootsResultSchema, {
9446
+ timeout: options?.timeout ?? 3e4
9447
+ })
9448
+ );
9416
9449
  registered.cachedRoots = result.roots;
9417
9450
  registered.rootsFetchedAt = Date.now();
9418
9451
  this.logger.verbose(
@@ -10221,7 +10254,7 @@ function callerSurface(ctx, surface) {
10221
10254
  }
10222
10255
  async function filterServableSkills(scope, skills, ctx, surface) {
10223
10256
  const caller = callerSurface(ctx, surface);
10224
- const offered = skills.filter((skill) => isOfferedOnSurface(skill.metadata.availableWhen, caller));
10257
+ const offered = skills.filter((skill) => availabilityForCall(skill.metadata.availableWhen, caller) === "available");
10225
10258
  if (offered.length === 0) return [];
10226
10259
  const { skills: servable } = await scope.runFlowForOutput("skills:filter", { skills: [...offered], ctx });
10227
10260
  const servableSkills = new Set(servable);
@@ -11889,7 +11922,8 @@ function createMcpHandlers(options) {
11889
11922
  const hasPromptsOrResources = options.scope?.prompts?.hasAny() || options.scope?.resources?.hasAny();
11890
11923
  const completionHandler = hasPromptsOrResources && options.serverOptions?.capabilities?.completions ? [completeRequestHandler(options)] : [];
11891
11924
  const loggingHandler = options.serverOptions?.capabilities?.logging ? [LoggingSetLevelRequestHandler(options)] : [];
11892
- const skillsHandler = options.scope?.skills?.hasAny() ? [skillsSearchRequestHandler(options), skillsLoadRequestHandler(options), skillsListRequestHandler(options)] : [];
11925
+ const skillRegistry = options.scope?.skills;
11926
+ const skillsHandler = skillRegistry?.servesSkills?.() ?? skillRegistry?.hasAny() ? [skillsSearchRequestHandler(options), skillsLoadRequestHandler(options), skillsListRequestHandler(options)] : [];
11893
11927
  const tasksHandler = options.serverOptions?.capabilities?.["tasks"] ? [
11894
11928
  tasksGetRequestHandler(options),
11895
11929
  tasksResultRequestHandler(options),
@@ -11944,7 +11978,7 @@ var in_memory_server_exports = {};
11944
11978
  __export(in_memory_server_exports, {
11945
11979
  createInMemoryServer: () => createInMemoryServer
11946
11980
  });
11947
- import { randomUUID as randomUUID4 } from "@frontmcp/utils";
11981
+ import { randomUUID as randomUUID4, runRequestExclusive as runRequestExclusive2 } from "@frontmcp/utils";
11948
11982
  async function createInMemoryServer(scope, options) {
11949
11983
  const { InMemoryTransport, McpServer: McpServer2 } = await import("@frontmcp/protocol");
11950
11984
  const { createMcpHandlers: createMcpHandlers2 } = await Promise.resolve().then(() => (init_mcp_handlers(), mcp_handlers_exports));
@@ -11964,7 +11998,7 @@ async function createInMemoryServer(scope, options) {
11964
11998
  sessionId
11965
11999
  }
11966
12000
  };
11967
- return originalHandler(request, enrichedCtx);
12001
+ return runRequestExclusive2(() => originalHandler(request, enrichedCtx));
11968
12002
  };
11969
12003
  mcpServer.setRequestHandler(handler.requestSchema, wrappedHandler);
11970
12004
  }
@@ -12612,7 +12646,7 @@ var init_connect = __esm({
12612
12646
  });
12613
12647
 
12614
12648
  // libs/sdk/src/direct/direct-server.ts
12615
- import { randomUUID as randomUUID6, sha256Hex } from "@frontmcp/utils";
12649
+ import { randomUUID as randomUUID6, runRequestExclusive as runRequestExclusive3, sha256Hex } from "@frontmcp/utils";
12616
12650
  function buildAuthInfo(authContext, defaultSessionId) {
12617
12651
  if (!authContext && !defaultSessionId) {
12618
12652
  return void 0;
@@ -12687,7 +12721,7 @@ var init_direct_server = __esm({
12687
12721
  }
12688
12722
  const ctx = this.buildHandlerContext(options);
12689
12723
  try {
12690
- return await this.scope.runFlowForOutput(flowName, { request, ctx });
12724
+ return await runRequestExclusive3(() => this.scope.runFlowForOutput(flowName, { request, ctx }));
12691
12725
  } catch (e) {
12692
12726
  if (e instanceof FlowControl && e.type === "respond") {
12693
12727
  return e.output;
@@ -14116,11 +14150,19 @@ var init_frontmcp_context = __esm({
14116
14150
  updateAuthInfo(authInfo) {
14117
14151
  this._authInfo = { ...this._authInfo, ...authInfo };
14118
14152
  }
14119
- /** `sessionId` when the server verified it; undefined for stateless and per-request placeholder ids. */
14153
+ /**
14154
+ * The session the server verified for this request; undefined for a request without one (MCP
14155
+ * 2026-07-28, the stateless transports, an `mcp-session-id` the server did not accept).
14156
+ *
14157
+ * Over HTTP that is the session session verification recorded (`authInfo.extra.sessionId`),
14158
+ * which it records only for a session the request presented: its `mcp-session-id` header, or the
14159
+ * `?sessionId=` a legacy SSE client posts its messages with. Over an in-process transport (stdio,
14160
+ * `connect()`, `createDirect()`) it is the session the transport runs the request in.
14161
+ */
14120
14162
  get verifiedSessionId() {
14121
- if (this.sessionId === STATELESS_SESSION_ID) return void 0;
14122
- const verified = this._authInfo.sessionId ?? this._authInfo.extra?.["sessionId"];
14123
- return verified === this.sessionId ? this.sessionId : void 0;
14163
+ const recorded = this._authInfo.extra?.["sessionId"];
14164
+ const verified = typeof recorded === "string" && recorded.length > 0 ? recorded : this._authInfo.sessionId === this.sessionId ? this.sessionId : void 0;
14165
+ return verified === STATELESS_SESSION_ID ? void 0 : verified;
14124
14166
  }
14125
14167
  /**
14126
14168
  * The client's self-reported identity, sent with this request (MCP 2026-07-28 `_meta`).
@@ -15863,6 +15905,9 @@ function registerHookOnClass(ctor, meta) {
15863
15905
  function resolvePendingTC39HooksForClass(ctor) {
15864
15906
  return pendingHookRegistry.resolveForClass(ctor);
15865
15907
  }
15908
+ function peekPendingTC39HooksForClass(ctor) {
15909
+ return pendingHookRegistry.resolveForClass(ctor, false);
15910
+ }
15866
15911
  function registerPendingTC39Hook(method, meta) {
15867
15912
  pendingHookRegistry.store(method, meta);
15868
15913
  }
@@ -16008,7 +16053,7 @@ var init_elicitation_types = __esm({
16008
16053
 
16009
16054
  // libs/sdk/src/elicitation/helpers/elicit.helper.ts
16010
16055
  import { toJSONSchema, z as z45 } from "@frontmcp/lazy-zod";
16011
- import { randomUUID as randomUUID10 } from "@frontmcp/utils";
16056
+ import { awaitOutsideRequest as awaitOutsideRequest2, randomUUID as randomUUID10 } from "@frontmcp/utils";
16012
16057
  async function performElicit(deps, message, requestedSchema, options) {
16013
16058
  const { sessionId, getClientCapabilities, tryGetContext, entryName, entryInput, elicitationEnabled } = deps;
16014
16059
  if (elicitationEnabled !== true) {
@@ -16056,7 +16101,7 @@ async function performElicit(deps, message, requestedSchema, options) {
16056
16101
  if (!transport) {
16057
16102
  throw new ElicitationNotSupportedError("Transport not available for elicitation");
16058
16103
  }
16059
- return transport.elicit(message, requestedSchema, options);
16104
+ return awaitOutsideRequest2(transport.elicit(message, requestedSchema, options));
16060
16105
  }
16061
16106
  function generateElicitationId() {
16062
16107
  return `elicit-${randomUUID10()}`;
@@ -18224,12 +18269,12 @@ function qualifiedNameOf(lineage, name44) {
18224
18269
  return `${ownerKeyOf(lineage)}:${name44}`;
18225
18270
  }
18226
18271
  function hookOwnerIdOf(lineage, owner) {
18227
- const owners = owner ? [...lineage, owner] : lineage;
18228
- return appOwnerIdOf(owners) ?? owners[owners.length - 1]?.id;
18272
+ const owners2 = owner ? [...lineage, owner] : lineage;
18273
+ return appOwnerIdOf(owners2) ?? owners2[owners2.length - 1]?.id;
18229
18274
  }
18230
18275
  function appOwnerIdOf(lineage, owner) {
18231
- const owners = owner ? [...lineage, owner] : lineage;
18232
- return owners.find((candidate) => candidate.kind === "app")?.id;
18276
+ const owners2 = owner ? [...lineage, owner] : lineage;
18277
+ return owners2.find((candidate) => candidate.kind === "app")?.id;
18233
18278
  }
18234
18279
  var EntryLineageIndex;
18235
18280
  var init_lineage_utils = __esm({
@@ -18327,6 +18372,7 @@ var init_get_prompt_flow = __esm({
18327
18372
  init_remote_capabilities_utils();
18328
18373
  init_common();
18329
18374
  init_availability();
18375
+ init_call_surface();
18330
18376
  init_errors();
18331
18377
  init_resolved_entries();
18332
18378
  init_hooks_utils();
@@ -18530,7 +18576,10 @@ var init_get_prompt_flow = __esm({
18530
18576
  }
18531
18577
  promptContext.mark("execute");
18532
18578
  try {
18533
- promptContext.output = await promptContext.execute(parsedArgs);
18579
+ promptContext.output = await runOnSurface(
18580
+ callSurfaceOf(this.input.ctx),
18581
+ async () => promptContext.execute(parsedArgs)
18582
+ );
18534
18583
  this.logger.verbose("execute:done");
18535
18584
  } catch (error) {
18536
18585
  if (error instanceof FlowControl || isClientFacingError(error)) throw error;
@@ -21615,6 +21664,7 @@ var init_send_elicitation_result_tool = __esm({
21615
21664
  "libs/sdk/src/elicitation/send-elicitation-result.tool.ts"() {
21616
21665
  "use strict";
21617
21666
  init_common();
21667
+ init_call_surface();
21618
21668
  init_errors();
21619
21669
  init_fallback_helper();
21620
21670
  inputSchema12 = {
@@ -21695,8 +21745,15 @@ var init_send_elicitation_result_tool = __esm({
21695
21745
  arguments: pending.toolInput
21696
21746
  }
21697
21747
  },
21698
- // Pass the context for auth info and request ID (used for elicitation routing)
21699
- ctx: this.tryGetContext() ?? { authInfo: this.getAuthInfo() }
21748
+ // The call context of this call, whose caller is the owner: its auth info as its transport
21749
+ // gave it (the tool reads it as `this.auth`), on the surface it was made from. Not the
21750
+ // request context object, whose auth info is a getter a call context does not carry over.
21751
+ ctx: {
21752
+ authInfo: this.authInfo,
21753
+ requestId: this.tryGetContext()?.requestId ?? this.runId,
21754
+ surface: getCallSurface(),
21755
+ ...this.signal ? { signal: this.signal } : {}
21756
+ }
21700
21757
  });
21701
21758
  this.logger.info("sendElicitationResult: original tool completed", {
21702
21759
  elicitId,
@@ -21874,7 +21931,14 @@ import {
21874
21931
  MCP_APPS_MIME_TYPE as MCP_APPS_MIME_TYPE2,
21875
21932
  resolveServingMode
21876
21933
  } from "@frontmcp/uipack/adapters";
21877
- import { findNonFiniteNumber, getRuntimeContext as getRuntimeContext7, isDebug, isDevelopment as isDevelopment2, randomUUID as randomUUID11 } from "@frontmcp/utils";
21934
+ import {
21935
+ awaitOutsideRequest as awaitOutsideRequest3,
21936
+ findNonFiniteNumber,
21937
+ getRuntimeContext as getRuntimeContext7,
21938
+ isDebug,
21939
+ isDevelopment as isDevelopment2,
21940
+ randomUUID as randomUUID11
21941
+ } from "@frontmcp/utils";
21878
21942
  function toolNameCandidates(name44) {
21879
21943
  if (!/[-_]/.test(name44)) return [name44];
21880
21944
  return [name44, name44.includes("_") ? name44.replace(/_/g, "-") : name44.replace(/-/g, "_")];
@@ -22138,6 +22202,8 @@ var init_call_tool_flow = __esm({
22138
22202
  request: { method: "tools/call", params: cleanedParams },
22139
22203
  scopeId: this.scope.id
22140
22204
  };
22205
+ const callSurface = callSurfaceOf(this.input.ctx);
22206
+ if (callSurface !== void 0) record.surface = callSurface;
22141
22207
  if (this.state.progressToken !== void 0) {
22142
22208
  record.progressToken = this.state.progressToken;
22143
22209
  }
@@ -22543,8 +22609,11 @@ var init_call_tool_flow = __esm({
22543
22609
  elicitId: error.elicitId,
22544
22610
  sessionId,
22545
22611
  owner,
22546
- toolName: error.toolName,
22547
- toolInput: error.toolInput,
22612
+ // The answer re-runs the call this flow ran: this tool, with these arguments. What asked
22613
+ // may be an entry the tool runs, like the agent behind `invoke_<agent>`, whose own name
22614
+ // no `tools/call` resolves.
22615
+ toolName: tool.fullName,
22616
+ toolInput: this.state.required.input.arguments,
22548
22617
  elicitMessage: error.elicitMessage,
22549
22618
  elicitSchema: error.schema,
22550
22619
  createdAt: Date.now(),
@@ -22556,7 +22625,7 @@ var init_call_tool_flow = __esm({
22556
22625
  elicitId: error.elicitId
22557
22626
  });
22558
22627
  const deps = { scope: this.scope, sessionId, logger: this.logger };
22559
- const result = await handleWaitingFallback(deps, error);
22628
+ const result = await awaitOutsideRequest3(handleWaitingFallback(deps, error));
22560
22629
  toolContext.output = result;
22561
22630
  this.logger.verbose("execute:done (elicitation waiting fallback)");
22562
22631
  return;
@@ -24430,6 +24499,39 @@ var init_server_validation = __esm({
24430
24499
  }
24431
24500
  });
24432
24501
 
24502
+ // libs/sdk/src/flows/flow.http-path.ts
24503
+ function matchMountedPath(pattern, path3) {
24504
+ if (!pattern.includes("/:")) {
24505
+ const prefix = pattern.endsWith("/") ? pattern : `${pattern}/`;
24506
+ return path3 === pattern || path3.startsWith(prefix) ? {} : void 0;
24507
+ }
24508
+ const mount = pattern.endsWith("/") ? pattern.slice(0, -1) : pattern;
24509
+ const expected = mount.split("/");
24510
+ const actual = path3.split("/");
24511
+ if (actual.length < expected.length) return void 0;
24512
+ const params = {};
24513
+ for (let i = 0; i < expected.length; i++) {
24514
+ const segment = expected[i];
24515
+ const value = actual[i];
24516
+ if (segment.length > 1 && segment.startsWith(":")) {
24517
+ if (value.length === 0) return void 0;
24518
+ try {
24519
+ params[segment.slice(1)] = decodeURIComponent(value);
24520
+ } catch {
24521
+ return void 0;
24522
+ }
24523
+ } else if (segment !== value) {
24524
+ return void 0;
24525
+ }
24526
+ }
24527
+ return params;
24528
+ }
24529
+ var init_flow_http_path = __esm({
24530
+ "libs/sdk/src/flows/flow.http-path.ts"() {
24531
+ "use strict";
24532
+ }
24533
+ });
24534
+
24433
24535
  // libs/sdk/src/flows/flow.stages.ts
24434
24536
  function sortStageMap(table) {
24435
24537
  for (const key of Object.keys(table)) {
@@ -24537,6 +24639,7 @@ var init_flow_instance = __esm({
24537
24639
  init_context();
24538
24640
  init_errors();
24539
24641
  init_server_validation();
24642
+ init_flow_http_path();
24540
24643
  init_flow_stages();
24541
24644
  FAILED_OUTCOMES = /* @__PURE__ */ new Set(["fail", "abort", "unknown_error"]);
24542
24645
  CAP = (s) => s ? s[0].toUpperCase() + s.slice(1) : s;
@@ -24656,12 +24759,16 @@ var init_flow_instance = __esm({
24656
24759
  const hasMwCanActivate = (mw.canActivate?.length ?? 0) > 0;
24657
24760
  if (!path3 && !hasStaticCanActivate && !hasMwCanActivate) return false;
24658
24761
  if (this.method && request.method !== this.method) return false;
24659
- if (path3) {
24660
- const reqPath = request.path;
24661
- const prefix = path3.endsWith("/") ? path3 : `${path3}/`;
24662
- if (reqPath !== path3 && !reqPath.startsWith(prefix)) return false;
24762
+ const captured = path3 ? matchMountedPath(path3, request.path) : {};
24763
+ if (!captured) return false;
24764
+ const previous = request.params;
24765
+ if (Object.keys(captured).length > 0) request.params = { ...captured, ...previous };
24766
+ const activated = await this.canActivate(request);
24767
+ if (!activated) {
24768
+ if (previous === void 0) delete request.params;
24769
+ else request.params = previous;
24663
24770
  }
24664
- return this.canActivate(request);
24771
+ return activated;
24665
24772
  }
24666
24773
  /**
24667
24774
  * Get FrontMcpContextStorage from providers (with fallback).
@@ -25656,6 +25763,11 @@ var init_execution_context_interface = __esm({
25656
25763
  * Authority/authorization checks still run — an internal call carries the
25657
25764
  * same `authInfo` as the surrounding request, so ABAC/RBAC guards apply.
25658
25765
  *
25766
+ * `availableWhen.surface`: the call carries {@link callToolSurface}. From a tool, resource or
25767
+ * prompt that is none (in-process dispatch, which no surface restricts); a job's call carries
25768
+ * `'job'`, an agent's `'agent'`, and an HTTP trigger's `'http-trigger'`, so a tool whose `surface`
25769
+ * leaves that caller out answers as an unknown tool.
25770
+ *
25659
25771
  * @param name Tool name (or fully-qualified `owner.name`).
25660
25772
  * @param args Tool arguments — validated by the tool's input schema.
25661
25773
  * @param opts Optional progress token / abort signal forwarded into `_meta`.
@@ -25673,10 +25785,12 @@ var init_execution_context_interface = __esm({
25673
25785
  ...Object.keys(requestMeta).length > 0 && { _meta: requestMeta }
25674
25786
  }
25675
25787
  };
25788
+ const surface = this.callToolSurface();
25676
25789
  const ctx = {
25677
25790
  authInfo: this.getAuthInfo(),
25678
25791
  requestId: this.tryGetContext()?.requestId ?? this.runId,
25679
25792
  internalCall: true,
25793
+ ...surface && { surface },
25680
25794
  ...opts?.signal && { signal: opts.signal }
25681
25795
  };
25682
25796
  const result = await scope.runFlow("tools:call-tool", { request, ctx });
@@ -25685,6 +25799,14 @@ var init_execution_context_interface = __esm({
25685
25799
  }
25686
25800
  return result;
25687
25801
  }
25802
+ /**
25803
+ * The surface (`availableWhen.surface`) a {@link callTool} from this context calls on. None by
25804
+ * default: a tool, resource or prompt composing with other tools is in-process dispatch. Contexts
25805
+ * that are callers of their own (jobs, agents, HTTP triggers) name their surface.
25806
+ */
25807
+ callToolSurface() {
25808
+ return void 0;
25809
+ }
25688
25810
  /**
25689
25811
  * Fail the execution and trigger error handling.
25690
25812
  */
@@ -27491,12 +27613,16 @@ var init_skill_registry = __esm({
27491
27613
  * caller cannot remove a replacement registration.
27492
27614
  */
27493
27615
  dynamicGenerations = /* @__PURE__ */ new Map();
27616
+ /** Whether a plugin registers skills at runtime ({@link expectDynamicSkills}). */
27617
+ dynamicSkillsExpected = false;
27494
27618
  /** Adopted skills from child registries */
27495
27619
  adopted = /* @__PURE__ */ new Map();
27496
27620
  /** Children registries */
27497
27621
  children = /* @__PURE__ */ new Set();
27498
27622
  /** O(1) indexes */
27499
27623
  byQualifiedId = /* @__PURE__ */ new Map();
27624
+ /** Owner lineage per skill instance, rebuilt with the index so `lineageOf` is O(1). */
27625
+ lineages = new EntryLineageIndex();
27500
27626
  byName = /* @__PURE__ */ new Map();
27501
27627
  byOwnerAndName = /* @__PURE__ */ new Map();
27502
27628
  /** Version and emitter for change tracking */
@@ -27911,12 +28037,31 @@ var init_skill_registry = __esm({
27911
28037
  if (!page.hasMore || page.skills.length === 0) return all;
27912
28038
  }
27913
28039
  }
28040
+ /**
28041
+ * Owner lineage (root → leaf) of a skill, or undefined when no row holds it. A skill a plugin
28042
+ * contributes is owned by the plugin; its lineage names the app the plugin is installed on.
28043
+ */
28044
+ lineageOf(entry) {
28045
+ return this.lineages.lineageOf(entry);
28046
+ }
27914
28047
  /**
27915
28048
  * Check if any skills exist.
27916
28049
  */
27917
28050
  hasAny() {
27918
28051
  return this.listAllIndexed().length > 0;
27919
28052
  }
28053
+ /**
28054
+ * Record that a plugin registers skills at runtime ({@link registerSkillContent}), such as a
28055
+ * skill bundle loaded after the server starts. The scope then serves the skills capability and
28056
+ * methods from startup, answering with the skills registered so far.
28057
+ */
28058
+ expectDynamicSkills() {
28059
+ this.dynamicSkillsExpected = true;
28060
+ }
28061
+ /** Whether the scope serves skills: it has some, or a plugin registers them at runtime. */
28062
+ servesSkills() {
28063
+ return this.dynamicSkillsExpected || this.hasAny();
28064
+ }
27920
28065
  /**
27921
28066
  * Get total skill count.
27922
28067
  *
@@ -28060,7 +28205,7 @@ var init_skill_registry = __esm({
28060
28205
  * of the schema cutover see the declaration.
28061
28206
  */
28062
28207
  getCapabilities() {
28063
- if (!this.hasAny()) return {};
28208
+ if (!this.servesSkills()) return {};
28064
28209
  return {
28065
28210
  experimental: {
28066
28211
  [SEP_2640_EXTENSION_ID]: {}
@@ -28339,6 +28484,7 @@ var init_skill_registry = __esm({
28339
28484
  }
28340
28485
  reindex() {
28341
28486
  const effective = this.listAllIndexed();
28487
+ this.lineages.rebuild(effective);
28342
28488
  this.byQualifiedId.clear();
28343
28489
  this.byName.clear();
28344
28490
  this.byOwnerAndName.clear();
@@ -29263,13 +29409,15 @@ var init_agent_utils = __esm({
29263
29409
  // libs/sdk/src/agent/agent.instance.ts
29264
29410
  import { AuthorityDeniedError as AuthorityDeniedError6 } from "@frontmcp/auth";
29265
29411
  import { toJSONSchema as toJSONSchema2, z as z59 } from "@frontmcp/lazy-zod";
29266
- var VALID_AGENT_HOOK_FLOWS, AGENT_ONLY_METADATA_KEYS, AgentInstance, FunctionAgentContext;
29412
+ var VALID_AGENT_HOOK_FLOWS, AGENT_SURFACE, AGENT_ONLY_METADATA_KEYS, AgentInstance, FunctionAgentContext;
29267
29413
  var init_agent_instance = __esm({
29268
29414
  "libs/sdk/src/agent/agent.instance.ts"() {
29269
29415
  "use strict";
29270
29416
  init_config();
29271
29417
  init_common();
29418
+ init_availability();
29272
29419
  init_tool_decorator();
29420
+ init_call_surface();
29273
29421
  init_running_tool();
29274
29422
  init_errors();
29275
29423
  init_hooks_utils();
@@ -29279,6 +29427,7 @@ var init_agent_instance = __esm({
29279
29427
  init_agent_scope();
29280
29428
  init_agent_utils();
29281
29429
  VALID_AGENT_HOOK_FLOWS = ["agents:call-agent", "agents:list-agents"];
29430
+ AGENT_SURFACE = "agent";
29282
29431
  AGENT_ONLY_METADATA_KEYS = /* @__PURE__ */ new Set([
29283
29432
  "id",
29284
29433
  "name",
@@ -29655,10 +29804,17 @@ var init_agent_instance = __esm({
29655
29804
  }
29656
29805
  /**
29657
29806
  * Get tool definitions available to this agent.
29658
- * Returns only the agent's own tools (from @Agent({ tools: [...] })).
29807
+ * Returns only the agent's own tools (from @Agent({ tools: [...] })) that are offered to it.
29659
29808
  */
29660
29809
  getToolDefinitions() {
29661
- return buildAgentToolDefinitions(this.agentTools);
29810
+ return buildAgentToolDefinitions(this.getOfferedTools());
29811
+ }
29812
+ /**
29813
+ * The agent's tools its model may call: those whose `availableWhen.surface` offers them on the
29814
+ * `'agent'` surface. The others are neither shown to the model nor run for it.
29815
+ */
29816
+ getOfferedTools() {
29817
+ return this.agentTools.filter((tool) => isOfferedOnSurface(tool.metadata.availableWhen, AGENT_SURFACE));
29662
29818
  }
29663
29819
  /**
29664
29820
  * Create a tool executor for agent-scoped tools.
@@ -29681,12 +29837,13 @@ var init_agent_instance = __esm({
29681
29837
  createToolExecutor(ctx) {
29682
29838
  const useToolFlow = this.record.metadata.execution?.useToolFlow !== false;
29683
29839
  return async (toolName, args) => {
29684
- const tool = this.agentTools.find((t) => t.name === toolName || t.fullName === toolName);
29840
+ const offeredTools = this.getOfferedTools();
29841
+ const tool = offeredTools.find((t) => t.name === toolName || t.fullName === toolName);
29685
29842
  if (!tool) {
29686
29843
  throw new AgentToolNotFoundError(
29687
29844
  this.name,
29688
29845
  toolName,
29689
- this.agentTools.map((t) => t.name)
29846
+ offeredTools.map((t) => t.name)
29690
29847
  );
29691
29848
  }
29692
29849
  if (useToolFlow && this.agentScope) {
@@ -29697,16 +29854,19 @@ var init_agent_instance = __esm({
29697
29854
  },
29698
29855
  ctx: {
29699
29856
  authInfo: ctx.authInfo,
29700
- _skipUI: true
29857
+ _skipUI: true,
29701
29858
  // Skip UI rendering - agent returns structured data
29859
+ surface: AGENT_SURFACE
29702
29860
  }
29703
29861
  });
29704
29862
  return this.extractToolResult(result);
29705
29863
  }
29706
29864
  await this.assertToolAuthorized(tool, ctx.authInfo, args);
29707
29865
  const runningTool = { name: tool.name, fullName: tool.fullName };
29708
- const toolContext = runAsTool(runningTool, () => tool.create(args, ctx));
29709
- return runAsTool(runningTool, () => Promise.resolve(toolContext.execute(args)));
29866
+ return runOnSurface(AGENT_SURFACE, () => {
29867
+ const toolContext = runAsTool(runningTool, () => tool.create(args, ctx));
29868
+ return runAsTool(runningTool, () => Promise.resolve(toolContext.execute(args)));
29869
+ });
29710
29870
  };
29711
29871
  }
29712
29872
  /**
@@ -35442,13 +35602,13 @@ var init_esm_module_loader = __esm({
35442
35602
  URL.revokeObjectURL(url);
35443
35603
  }
35444
35604
  }
35445
- const { ensureDir, fileExists: fileExists9, sha256Hex: sha256Hex13, writeFile } = await import("@frontmcp/utils");
35605
+ const { ensureDir, fileExists: fileExists9, sha256Hex: sha256Hex14, writeFile } = await import("@frontmcp/utils");
35446
35606
  const nodePath = await import("node:path");
35447
35607
  const nodeOs = await import("node:os");
35448
35608
  const { pathToFileURL } = await import("node:url");
35449
35609
  const dir = nodePath.join(nodeOs.tmpdir(), "frontmcp-esm-modules");
35450
35610
  await ensureDir(dir);
35451
- const tempPath = nodePath.join(dir, `${sha256Hex13(bundleContent)}.mjs`);
35611
+ const tempPath = nodePath.join(dir, `${sha256Hex14(bundleContent)}.mjs`);
35452
35612
  if (!await fileExists9(tempPath)) {
35453
35613
  await writeFile(tempPath, bundleContent);
35454
35614
  }
@@ -37187,7 +37347,7 @@ var init_oauth_authorize_flow = __esm({
37187
37347
  });
37188
37348
  inputSchema16 = httpInputSchema;
37189
37349
  stateSchema15 = z65.object({
37190
- isDefaultAuthProvider: z65.boolean().describe("If FrontMcp initialized without auth options"),
37350
+ isDefaultAuthProvider: z65.boolean().describe("No sign-in to run: no auth options, or public mode (the same configuration, written out)"),
37191
37351
  isOrchestrated: z65.boolean().describe("If FrontMcp is orchestrated (local oauth proxy, remote oauth proxy)"),
37192
37352
  allowAnonymous: z65.boolean().describe("Allow anonymous access, force orchestrated mode"),
37193
37353
  // Validated OAuth request (after validation)
@@ -37253,9 +37413,13 @@ var init_oauth_authorize_flow = __esm({
37253
37413
  * Returns `undefined` when the configured auth instance has no issuer, so the
37254
37414
  * parameter is simply omitted rather than emitted empty — a client validating
37255
37415
  * `iss` treats absence as "not supported" and proceeds.
37416
+ *
37417
+ * With `request`, the issuer the tokens redeemed for this request name
37418
+ * (`LocalPrimaryAuth.issuerFor`); without it, the boot-time issuer.
37256
37419
  */
37257
- resolveIssuer() {
37258
- const issuer = this.scope.auth?.issuer;
37420
+ resolveIssuer(request) {
37421
+ const auth = this.scope.auth;
37422
+ const issuer = request && auth?.issuerFor ? auth.issuerFor(request) : auth?.issuer;
37259
37423
  return typeof issuer === "string" && issuer.length > 0 ? issuer : void 0;
37260
37424
  }
37261
37425
  logger = this.scope.logger.child("OauthAuthorizeFlow");
@@ -37295,7 +37459,7 @@ var init_oauth_authorize_flow = __esm({
37295
37459
  }
37296
37460
  }
37297
37461
  const existingSessionId = request.query["session_id"];
37298
- const isDefaultAuthProvider = !metadata.auth;
37462
+ const isDefaultAuthProvider = !metadata.auth || isPublicMode(metadata.auth);
37299
37463
  let requiresFederatedLogin = false;
37300
37464
  if (metadata.auth && isOrchestratedMode(metadata.auth)) {
37301
37465
  const configProviders = getConfiguredProviderIds(metadata.auth);
@@ -37368,11 +37532,26 @@ var init_oauth_authorize_flow = __esm({
37368
37532
  if (result2.data.state) {
37369
37533
  url.searchParams.set("state", result2.data.state);
37370
37534
  }
37371
- const issuer = this.resolveIssuer();
37535
+ const issuer = this.resolveIssuer(this.rawInput.request);
37372
37536
  if (issuer) url.searchParams.set("iss", issuer);
37373
37537
  this.respond(httpRespond.redirect(url.toString()));
37374
37538
  return;
37375
37539
  }
37540
+ const authConfig = this.scope.metadata.auth;
37541
+ if (!authConfig || !isOrchestratedMode(authConfig)) {
37542
+ const mode = authConfig?.mode ?? "public";
37543
+ this.logger.warn(`Authorization request refused: auth mode ${mode} does not sign users in`);
37544
+ this.respond(
37545
+ this.htmlPage(
37546
+ this.renderErrorPage(
37547
+ "unsupported_response_type",
37548
+ `This server does not sign users in (auth mode: ${mode}). Present its access token instead.`
37549
+ ),
37550
+ 400
37551
+ )
37552
+ );
37553
+ return;
37554
+ }
37376
37555
  const result = oauthAuthorizeRequestSchema.safeParse({
37377
37556
  response_type: request.query["response_type"],
37378
37557
  client_id: request.query["client_id"],
@@ -37408,10 +37587,10 @@ var init_oauth_authorize_flow = __esm({
37408
37587
  const redirectAllowlisted = !!registry?.hasRedirectAllowlist() && registry.isRedirectUriAllowed(redirect_uri);
37409
37588
  let redirectVerified = redirectAllowlisted;
37410
37589
  const isCimdClientId2 = !!cimdService?.enabled && cimdService.isCimdClientId(client_id);
37411
- const dcrError = this.checkDcrAllowlist(client_id, redirect_uri, isCimdClientId2);
37412
- if (dcrError) {
37413
- this.logger.warn(`OAuth authorize: DCR allowlist rejection \u2014 ${dcrError}`);
37414
- this.respondWithError([dcrError], redirectVerified ? redirect_uri : void 0, rawState);
37590
+ const redirectError = this.checkDcrRedirectAllowlist(redirect_uri);
37591
+ if (redirectError) {
37592
+ this.logger.warn(`OAuth authorize: DCR allowlist rejection \u2014 ${redirectError}`);
37593
+ this.respondWithError([redirectError], void 0, rawState);
37415
37594
  return;
37416
37595
  }
37417
37596
  if (isCimdClientId2) {
@@ -37435,24 +37614,34 @@ var init_oauth_authorize_flow = __esm({
37435
37614
  return;
37436
37615
  }
37437
37616
  }
37438
- if (!isCimdClientId2) {
37439
- const registered = registry?.get?.(client_id);
37440
- if (registered) {
37441
- if (!registered.redirect_uris.includes(redirect_uri)) {
37442
- this.logger.warn(`OAuth authorize: redirect_uri "${redirect_uri}" not registered for client "${client_id}"`);
37443
- this.respondWithError(["redirect_uri is not registered for this client"], void 0, rawState);
37444
- return;
37445
- }
37446
- redirectVerified = true;
37447
- } else if (authOptions?.requireRegisteredClients !== false) {
37448
- this.logger.warn(`OAuth authorize: rejecting unregistered client_id "${client_id}" (requireRegisteredClients)`);
37449
- this.respondWithError(
37450
- ["Unknown client_id: register the client (DCR / pre-registered) or use a CIMD client-id URL"],
37451
- void 0,
37452
- rawState
37453
- );
37617
+ const registered = isCimdClientId2 ? void 0 : registry?.get?.(client_id);
37618
+ if (registered) {
37619
+ if (!registered.redirect_uris.includes(redirect_uri)) {
37620
+ this.logger.warn(`OAuth authorize: redirect_uri "${redirect_uri}" not registered for client "${client_id}"`);
37621
+ this.respondWithError(["redirect_uri is not registered for this client"], void 0, rawState);
37454
37622
  return;
37455
37623
  }
37624
+ redirectVerified = true;
37625
+ }
37626
+ const clientIdError = isCimdClientId2 ? void 0 : this.checkDcrClientIdAllowlist(client_id);
37627
+ if (clientIdError) {
37628
+ this.logger.warn(`OAuth authorize: DCR allowlist rejection \u2014 ${clientIdError}`);
37629
+ this.respondWithError(
37630
+ [clientIdError],
37631
+ redirectVerified ? redirect_uri : void 0,
37632
+ rawState,
37633
+ "unauthorized_client"
37634
+ );
37635
+ return;
37636
+ }
37637
+ if (!isCimdClientId2 && !registered && authOptions?.requireRegisteredClients !== false) {
37638
+ this.logger.warn(`OAuth authorize: rejecting unregistered client_id "${client_id}" (requireRegisteredClients)`);
37639
+ this.respondWithError(
37640
+ ["Unknown client_id: register the client (DCR / pre-registered) or use a CIMD client-id URL"],
37641
+ void 0,
37642
+ rawState
37643
+ );
37644
+ return;
37456
37645
  }
37457
37646
  const canonicalResource = computeResource(request, this.scope.entryPath, this.scope.routeBase);
37458
37647
  if (!validatedRequest.resource) {
@@ -37893,26 +38082,28 @@ var init_oauth_authorize_flow = __esm({
37893
38082
  return httpRespond.html(markup, status, builtInAuthPageHeaders());
37894
38083
  }
37895
38084
  /**
37896
- * Enforce the local-AS DCR allowlists (#462) at authorize time. Returns a
37897
- * human-readable rejection reason, or `undefined` when the request is allowed
37898
- * (including when no allowlist is configured, or the auth instance does not
37899
- * expose a DCR registry — e.g. non-local modes).
37900
- *
37901
- * - `allowedRedirectUris`: applies to ALL clients (CIMD or not) as an
37902
- * open-redirect / lateral-movement guard.
37903
- * - `allowedClientIds`: applies only to non-CIMD client ids; CIMD URLs are
37904
- * validated by the CIMD layer instead.
38085
+ * Enforce the local-AS DCR `allowedRedirectUris` (#462) at authorize time,
38086
+ * for ALL clients (CIMD or not) as an open-redirect / lateral-movement guard.
38087
+ * Returns a human-readable rejection reason, or `undefined` when the
38088
+ * redirect_uri is allowed (including when no allowlist is configured, or the
38089
+ * auth instance does not expose a DCR registry — e.g. non-local modes).
37905
38090
  */
37906
- checkDcrAllowlist(clientId, redirectUri, isCimdClientId2) {
37907
- const auth = this.scope.auth;
37908
- const registry = auth?.dcrClientRegistry;
37909
- if (!registry) {
37910
- return void 0;
37911
- }
37912
- if (registry.hasRedirectAllowlist() && !registry.isRedirectUriAllowed(redirectUri)) {
38091
+ checkDcrRedirectAllowlist(redirectUri) {
38092
+ const registry = this.scope.auth?.dcrClientRegistry;
38093
+ if (registry?.hasRedirectAllowlist() && !registry.isRedirectUriAllowed(redirectUri)) {
37913
38094
  return `redirect_uri "${redirectUri}" is not in the configured allowlist`;
37914
38095
  }
37915
- if (!isCimdClientId2 && registry.hasClientIdAllowlist() && !registry.isClientIdAllowed(clientId)) {
38096
+ return void 0;
38097
+ }
38098
+ /**
38099
+ * Enforce the local-AS DCR `allowedClientIds` (#462) at authorize time, for
38100
+ * non-CIMD client ids (the caller skips CIMD URLs, which the CIMD layer
38101
+ * validates). Returns a human-readable rejection reason, or `undefined` when
38102
+ * the client is allowed or no allowlist is configured.
38103
+ */
38104
+ checkDcrClientIdAllowlist(clientId) {
38105
+ const registry = this.scope.auth?.dcrClientRegistry;
38106
+ if (registry?.hasClientIdAllowlist() && !registry.isClientIdAllowed(clientId)) {
37916
38107
  return `client_id "${clientId}" is not in the configured allowlist`;
37917
38108
  }
37918
38109
  return void 0;
@@ -37927,15 +38118,18 @@ var init_oauth_authorize_flow = __esm({
37927
38118
  });
37928
38119
  }
37929
38120
  /**
37930
- * Respond with OAuth error - redirect if possible, otherwise show error page
38121
+ * Respond with OAuth error - redirect if possible, otherwise show error page.
38122
+ *
38123
+ * @param redirectUri A redirect_uri validated for the client; omit it for an error page.
38124
+ * @param error The RFC 6749 §4.1.2.1 error code. Default `invalid_request`.
37931
38125
  */
37932
- respondWithError(errors, redirectUri, state) {
38126
+ respondWithError(errors, redirectUri, state, error = "invalid_request") {
37933
38127
  const errorDescription = errors.join("; ");
37934
38128
  if (redirectUri) {
37935
38129
  const safe = safeRedirectUriSchema.safeParse(redirectUri);
37936
38130
  if (safe.success) {
37937
38131
  const url = new URL(safe.data);
37938
- url.searchParams.set("error", "invalid_request");
38132
+ url.searchParams.set("error", error);
37939
38133
  url.searchParams.set("error_description", errorDescription);
37940
38134
  if (state) {
37941
38135
  url.searchParams.set("state", state);
@@ -37944,7 +38138,7 @@ var init_oauth_authorize_flow = __esm({
37944
38138
  return;
37945
38139
  }
37946
38140
  }
37947
- this.respond(this.htmlPage(this.renderErrorPage("invalid_request", errorDescription), 400));
38141
+ this.respond(this.htmlPage(this.renderErrorPage(error, errorDescription), 400));
37948
38142
  }
37949
38143
  /**
37950
38144
  * Render the local login page.
@@ -38223,9 +38417,13 @@ var init_oauth_callback_flow = __esm({
38223
38417
  * Returns `undefined` when the configured auth instance has no issuer, so the
38224
38418
  * parameter is simply omitted rather than emitted empty — a client validating
38225
38419
  * `iss` treats absence as "not supported" and proceeds.
38420
+ *
38421
+ * With `request`, the issuer the tokens redeemed for this request name
38422
+ * (`LocalPrimaryAuth.issuerFor`); without it, the boot-time issuer.
38226
38423
  */
38227
- resolveIssuer() {
38228
- const issuer = this.scope.auth?.issuer;
38424
+ resolveIssuer(request) {
38425
+ const auth = this.scope.auth;
38426
+ const issuer = request && auth?.issuerFor ? auth.issuerFor(request) : auth?.issuer;
38229
38427
  return typeof issuer === "string" && issuer.length > 0 ? issuer : void 0;
38230
38428
  }
38231
38429
  async checkIpFilter() {
@@ -38763,7 +38961,7 @@ var init_oauth_callback_flow = __esm({
38763
38961
  if (originalState) {
38764
38962
  url.searchParams.set("state", originalState);
38765
38963
  }
38766
- const issuer = this.resolveIssuer();
38964
+ const issuer = this.resolveIssuer(this.rawInput.request);
38767
38965
  if (issuer) url.searchParams.set("iss", issuer);
38768
38966
  if (isIncremental && targetAppId) {
38769
38967
  url.searchParams.set("incremental", "true");
@@ -40665,7 +40863,7 @@ var init_oauth_token_flow = __esm({
40665
40863
  stateSchema20 = z70.object({
40666
40864
  body: tokenRequestSchema.optional(),
40667
40865
  grantType: z70.enum(["authorization_code", "refresh_token", "anonymous"]).optional(),
40668
- isDefaultAuthProvider: z70.boolean().describe("If FrontMcp initialized without auth options"),
40866
+ isDefaultAuthProvider: z70.boolean().describe("No sign-in to run: no auth options, or public mode (the same configuration, written out)"),
40669
40867
  isOrchestrated: z70.boolean().describe("If auth mode is orchestrated"),
40670
40868
  // Token response data
40671
40869
  tokenResponse: z70.object({
@@ -40695,7 +40893,7 @@ var init_oauth_token_flow = __esm({
40695
40893
  async parseInput() {
40696
40894
  const { metadata } = this.scope;
40697
40895
  const { request } = this.rawInput;
40698
- const isDefaultAuthProvider = !metadata.auth;
40896
+ const isDefaultAuthProvider = !metadata.auth || isPublicMode(metadata.auth);
40699
40897
  const isOrchestrated = !isDefaultAuthProvider;
40700
40898
  const rawBody = coerceTokenRequestBody(request);
40701
40899
  const basic = parseBasicAuthorization(request.headers?.["authorization"]);
@@ -40744,7 +40942,10 @@ var init_oauth_token_flow = __esm({
40744
40942
  const authOptions = this.scope.auth?.options;
40745
40943
  if (isDefaultAuthProvider && body.code === "anonymous" && !!authOptions && allowsPublicAccess(authOptions)) {
40746
40944
  const localAuth2 = this.scope.auth;
40747
- const accessToken = await localAuth2.signAnonymousJwt({ audience: this.canonicalResource() });
40945
+ const accessToken = await localAuth2.signAnonymousJwt({
40946
+ audience: this.canonicalResource(),
40947
+ issuer: localAuth2.issuerFor(this.rawInput.request)
40948
+ });
40748
40949
  this.state.set("tokenResponse", {
40749
40950
  access_token: accessToken,
40750
40951
  token_type: "Bearer",
@@ -40760,7 +40961,8 @@ var init_oauth_token_flow = __esm({
40760
40961
  body.redirect_uri,
40761
40962
  body.code_verifier,
40762
40963
  body.client_secret,
40763
- this.canonicalResource()
40964
+ this.canonicalResource(),
40965
+ localAuth.issuerFor(this.rawInput.request)
40764
40966
  );
40765
40967
  if ("error" in result) {
40766
40968
  this.logger.warn(`Code exchange failed: ${result.error}`);
@@ -40798,7 +41000,10 @@ var init_oauth_token_flow = __esm({
40798
41000
  return;
40799
41001
  }
40800
41002
  const localAuth2 = this.scope.auth;
40801
- const accessToken = await localAuth2.signAnonymousJwt({ audience: this.canonicalResource() });
41003
+ const accessToken = await localAuth2.signAnonymousJwt({
41004
+ audience: this.canonicalResource(),
41005
+ issuer: localAuth2.issuerFor(this.rawInput.request)
41006
+ });
40802
41007
  this.state.set("tokenResponse", {
40803
41008
  access_token: accessToken,
40804
41009
  token_type: "Bearer",
@@ -40812,7 +41017,8 @@ var init_oauth_token_flow = __esm({
40812
41017
  body.refresh_token,
40813
41018
  body.client_id,
40814
41019
  body.client_secret,
40815
- this.canonicalResource()
41020
+ this.canonicalResource(),
41021
+ localAuth.issuerFor(this.rawInput.request)
40816
41022
  );
40817
41023
  if ("error" in result) {
40818
41024
  this.logger.warn(`Refresh token failed: ${result.error}`);
@@ -40868,7 +41074,10 @@ var init_oauth_token_flow = __esm({
40868
41074
  }
40869
41075
  const localAuth = this.scope.auth;
40870
41076
  const audience = body?.grant_type === "anonymous" && body.resource ? body.resource : canonicalResource;
40871
- const accessToken = await localAuth.signAnonymousJwt({ audience });
41077
+ const accessToken = await localAuth.signAnonymousJwt({
41078
+ audience,
41079
+ issuer: localAuth.issuerFor(this.rawInput.request)
41080
+ });
40872
41081
  this.state.set("tokenResponse", {
40873
41082
  access_token: accessToken,
40874
41083
  token_type: "Bearer",
@@ -40996,7 +41205,8 @@ var init_oauth_userinfo_flow = __esm({
40996
41205
  return;
40997
41206
  }
40998
41207
  const localAuth = this.scope.auth;
40999
- const result = await localAuth.verifyGatewayToken(token, baseUrl);
41208
+ const issuer = localAuth.issuerFor?.(this.rawInput.request);
41209
+ const result = await localAuth.verifyGatewayToken(token, baseUrl, void 0, issuer);
41000
41210
  if (!result.ok) {
41001
41211
  this.logger.warn("userinfo: token verification failed", { error: result.error });
41002
41212
  this.respond(
@@ -41374,7 +41584,8 @@ var init_session_verify_flow = __esm({
41374
41584
  } else {
41375
41585
  const configuredAudience = isOrchestratedMode(authOptions) ? authOptions.expectedAudience : void 0;
41376
41586
  const resource = computeResource(this.rawInput.request, this.scope.entryPath, this.scope.routeBase);
41377
- verify = auth.verifyGatewayToken(token, this.state.required.baseUrl, configuredAudience ?? resource);
41587
+ const issuer = auth.issuerFor?.(this.rawInput.request);
41588
+ verify = auth.verifyGatewayToken(token, this.state.required.baseUrl, configuredAudience ?? resource, issuer);
41378
41589
  }
41379
41590
  const result = await verify;
41380
41591
  if (result.ok) {
@@ -41621,6 +41832,7 @@ var init_well_known_jwks_flow = __esm({
41621
41832
 
41622
41833
  // libs/sdk/src/auth/flows/well-known.oauth-authorization-server.flow.ts
41623
41834
  import "reflect-metadata";
41835
+ import { advertisedScopes } from "@frontmcp/auth";
41624
41836
  import { z as z74 } from "@frontmcp/lazy-zod";
41625
41837
  import { isProduction as isProduction4 } from "@frontmcp/utils";
41626
41838
  function resolveDcrEnabled(auth) {
@@ -41705,7 +41917,9 @@ var init_well_known_oauth_authorization_server_flow = __esm({
41705
41917
  wellKnownAsStateSchema.parse({
41706
41918
  baseUrl,
41707
41919
  oauthBaseUrl,
41708
- scopesSupported: [],
41920
+ // The scopes this server grants (`allowedScopes`, #262): the literal
41921
+ // entries; a `*` glob names no scope a client could ask for.
41922
+ scopesSupported: metadata.auth && isOrchestratedMode(metadata.auth) ? advertisedScopes(metadata.auth.allowedScopes) : [],
41709
41923
  tokenEndpointAuthMethods: [],
41710
41924
  // #462 — advertise registration_endpoint only when local-AS DCR is
41711
41925
  // actually active (explicit auth.dcr.enabled, else dev/prod default).
@@ -41789,6 +42003,7 @@ var init_well_known_oauth_authorization_server_flow = __esm({
41789
42003
 
41790
42004
  // libs/sdk/src/auth/flows/well-known.prm.flow.ts
41791
42005
  import "reflect-metadata";
42006
+ import { advertisedScopes as advertisedScopes2 } from "@frontmcp/auth";
41792
42007
  import { z as z75 } from "@frontmcp/lazy-zod";
41793
42008
  var inputSchema26, stateSchema24, outputSchema24, plan24, name25, Stage25, WellKnownPrmFlow;
41794
42009
  var init_well_known_prm_flow = __esm({
@@ -41831,11 +42046,12 @@ var init_well_known_prm_flow = __esm({
41831
42046
  if (!request) throw new FlowInputMissingError("request", "well-known:prm");
41832
42047
  const resource = computeResource(request, scope.entryPath, scope.routeBase);
41833
42048
  const baseUrl = getRequestBaseUrl(request, scope.entryPath);
42049
+ const auth = scope.metadata.auth;
41834
42050
  this.state.set(
41835
42051
  stateSchema24.parse({
41836
42052
  resource,
41837
42053
  baseUrl,
41838
- scopesSupported: scope.getAllSupportedScopes(),
42054
+ scopesSupported: auth && isOrchestratedMode(auth) ? advertisedScopes2(auth.allowedScopes) : scope.getAllSupportedScopes(),
41839
42055
  isOrchestrated: false
41840
42056
  //scope.orchestrated,// TODO: fix
41841
42057
  })
@@ -41899,6 +42115,24 @@ var init_well_known_prm_flow = __esm({
41899
42115
  }
41900
42116
  });
41901
42117
 
42118
+ // libs/sdk/src/auth/session-scope-identity.ts
42119
+ import { isAnonymousSubject as isAnonymousSubject3 } from "@frontmcp/auth";
42120
+ function sessionScopeIdentity(ctx) {
42121
+ const verified = ctx.verifiedSessionId;
42122
+ if (verified) return verified;
42123
+ const authInfo = ctx.authInfo ?? {};
42124
+ const extraUser = authInfo.extra?.["user"];
42125
+ const principal = [authInfo.user?.sub, extraUser?.sub, authInfo.clientId].find(
42126
+ (candidate) => !isAnonymousSubject3(candidate)
42127
+ );
42128
+ return principal ? `principal:${principal}` : void 0;
42129
+ }
42130
+ var init_session_scope_identity = __esm({
42131
+ "libs/sdk/src/auth/session-scope-identity.ts"() {
42132
+ "use strict";
42133
+ }
42134
+ });
42135
+
41902
42136
  // libs/sdk/src/auth/secure-store/secure-store.providers.ts
41903
42137
  import {
41904
42138
  SECURE_STORE_ACCESSOR,
@@ -41906,8 +42140,9 @@ import {
41906
42140
  } from "@frontmcp/auth";
41907
42141
  import { ProviderScope as ProviderScope6 } from "@frontmcp/di";
41908
42142
  function resolveRequestSessionId(ctx) {
41909
- const sid = ctx.sessionId;
41910
- return typeof sid === "string" && sid.length > 0 ? sid : void 0;
42143
+ const identity = sessionScopeIdentity(ctx);
42144
+ if (identity) return identity;
42145
+ throw new SessionIdentityRequiredError("The secure store's session scope");
41911
42146
  }
41912
42147
  function createSecureStoreProviders(opts) {
41913
42148
  const { backend, scope, ttlMs } = opts;
@@ -41939,7 +42174,9 @@ var init_secure_store_providers = __esm({
41939
42174
  "use strict";
41940
42175
  init_common();
41941
42176
  init_frontmcp_context_provider();
42177
+ init_session_identity_required_error();
41942
42178
  init_credentials_providers();
42179
+ init_session_scope_identity();
41943
42180
  SECURE_STORE_BACKEND = /* @__PURE__ */ Symbol.for("frontmcp:SECURE_STORE_BACKEND");
41944
42181
  }
41945
42182
  });
@@ -42079,6 +42316,7 @@ var init_instance_local_primary_auth = __esm({
42079
42316
  this.port = this.providers.getActiveScope().metadata.http?.port ?? 3001;
42080
42317
  this.host = getEnv4("FRONTMCP_PUBLIC_HOST")?.trim() || "localhost";
42081
42318
  this.issuer = this.deriveIssuer(options);
42319
+ this.issuerIsConfigured = this.configuredIssuer(options) !== void 0 || !!getEnv4("FRONTMCP_PUBLIC_HOST")?.trim();
42082
42320
  const jwtSecret = getEnv4("JWT_SECRET");
42083
42321
  const hasJwtSecret = !!jwtSecret?.trim();
42084
42322
  if (hasJwtSecret && jwtSecret) {
@@ -42117,7 +42355,10 @@ var init_instance_local_primary_auth = __esm({
42117
42355
  providers;
42118
42356
  host;
42119
42357
  port;
42358
+ /** The boot-time issuer: the configured one, else this server's listener address (see {@link issuerFor}). */
42120
42359
  issuer;
42360
+ /** Whether the operator named the issuer (`issuer` / `local.issuer`, or its host with `FRONTMCP_PUBLIC_HOST`). */
42361
+ issuerIsConfigured;
42121
42362
  keys = [];
42122
42363
  secret;
42123
42364
  logger;
@@ -42266,18 +42507,36 @@ var init_instance_local_primary_auth = __esm({
42266
42507
  * supported knob for aligning the issuer with discovery.
42267
42508
  */
42268
42509
  deriveIssuer(options) {
42269
- const basePath = `http://${this.host}:${this.port}${this.scope.fullPath}`;
42270
- if (isPublicMode(options)) {
42271
- return options.issuer ?? basePath;
42272
- }
42273
- if (isOrchestratedMode(options)) {
42274
- if (isOrchestratedLocal(options)) {
42275
- return options.local?.issuer ?? basePath;
42276
- } else {
42277
- return options.local?.issuer ?? basePath;
42278
- }
42279
- }
42280
- return basePath;
42510
+ return this.configuredIssuer(options) ?? `http://${this.host}:${this.port}${this.scope.fullPath}`;
42511
+ }
42512
+ /** The issuer the options name: `issuer` in public mode, `local.issuer` in local and remote mode. */
42513
+ configuredIssuer(options) {
42514
+ if (isPublicMode(options)) return options.issuer;
42515
+ if (isOrchestratedMode(options)) return options.local?.issuer;
42516
+ return void 0;
42517
+ }
42518
+ /**
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:
42522
+ *
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.
42532
+ */
42533
+ issuerFor(request) {
42534
+ if (!request || this.issuerIsConfigured) return this.issuer;
42535
+ if (isPublicUrlPinned()) return computeIssuer(request, this.scope.entryPath, this.scope.routeBase);
42536
+ 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);
42281
42540
  }
42282
42541
  /**
42283
42542
  * Read the `tokenStorage` selection off the auth options. Only
@@ -42508,9 +42767,10 @@ var init_instance_local_primary_auth = __esm({
42508
42767
  * user, and its scopes are the configured `anonymousScopes`, never a role.
42509
42768
  *
42510
42769
  * @param options.audience The protected resource the token is for (`aud`, #269).
42770
+ * @param options.issuer The issuer to name (`iss`): {@link issuerFor} the request. Default: the boot-time issuer.
42511
42771
  */
42512
42772
  async signAnonymousJwt(options = {}) {
42513
- const jwt = new SignJWT({ sub: `anon:${randomUUID21()}`, anonymous: true, scope: this.anonymousScopes().join(" ") }).setProtectedHeader({ alg: "HS256", typ: "JWT" }).setIssuedAt().setIssuer(this.issuer).setExpirationTime("1d").setJti(randomUUID21());
42773
+ const jwt = new SignJWT({ sub: `anon:${randomUUID21()}`, anonymous: true, scope: this.anonymousScopes().join(" ") }).setProtectedHeader({ alg: "HS256", typ: "JWT" }).setIssuedAt().setIssuer(options.issuer ?? this.issuer).setExpirationTime("1d").setJti(randomUUID21());
42514
42774
  if (options.audience) jwt.setAudience(options.audience);
42515
42775
  return jwt.sign(this.secret);
42516
42776
  }
@@ -42527,8 +42787,10 @@ var init_instance_local_primary_auth = __esm({
42527
42787
  *
42528
42788
  * The secret alone doesn't make a token this server's: every server started
42529
42789
  * with the same JWT_SECRET holds it. So a token must also (#269):
42530
- * - name THIS instance as its issuer (`iss`, the boot-time issuer it signs
42531
- * with, never the request-derived base URL), and
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
42532
42794
  * - when `expectedAudience` is given, be issued for that protected resource,
42533
42795
  * or one of those resources (`aud`, compared as RFC 8707 resource URIs).
42534
42796
  * `session:verify` passes the request's resource URL, or the configured
@@ -42537,11 +42799,11 @@ var init_instance_local_primary_auth = __esm({
42537
42799
  * Lifetime: `exp` is required (#272) and checked with `nbf` by `jose`. The
42538
42800
  * algorithm is pinned to HS256 to block `alg` confusion.
42539
42801
  */
42540
- async verifyGatewayToken(token, requestBaseUrl, expectedAudience) {
42802
+ async verifyGatewayToken(token, requestBaseUrl, expectedAudience, expectedIssuer) {
42541
42803
  try {
42542
42804
  const { payload, protectedHeader } = await jwtVerify(token, this.secret, {
42543
42805
  algorithms: ["HS256"],
42544
- issuer: this.issuer,
42806
+ issuer: expectedIssuer ?? this.issuer,
42545
42807
  requiredClaims: ["exp"]
42546
42808
  });
42547
42809
  if (expectedAudience !== void 0 && !audienceMatchesResource(payload.aud, expectedAudience)) {
@@ -42562,7 +42824,7 @@ var init_instance_local_primary_auth = __esm({
42562
42824
  /**
42563
42825
  * Sign an access token for an authenticated user
42564
42826
  */
42565
- async signAccessToken(user, scopes, audience, consentMetadata) {
42827
+ async signAccessToken(user, scopes, audience, consentMetadata, issuer) {
42566
42828
  const claims = {
42567
42829
  sub: user.sub,
42568
42830
  scope: scopes.join(" ")
@@ -42598,7 +42860,7 @@ var init_instance_local_primary_auth = __esm({
42598
42860
  }
42599
42861
  }
42600
42862
  }
42601
- const jwt = new SignJWT(claims).setProtectedHeader({ alg: "HS256", typ: "JWT" }).setIssuedAt().setIssuer(this.issuer).setExpirationTime(`${this.accessTokenTtlSeconds}s`).setJti(randomUUID21());
42863
+ const jwt = new SignJWT(claims).setProtectedHeader({ alg: "HS256", typ: "JWT" }).setIssuedAt().setIssuer(issuer ?? this.issuer).setExpirationTime(`${this.accessTokenTtlSeconds}s`).setJti(randomUUID21());
42602
42864
  if (audience) {
42603
42865
  jwt.setAudience(audience);
42604
42866
  }
@@ -42607,7 +42869,7 @@ var init_instance_local_primary_auth = __esm({
42607
42869
  /**
42608
42870
  * Exchange an authorization code for tokens
42609
42871
  */
42610
- async exchangeCode(code, clientId, redirectUri, codeVerifier, clientSecret, defaultAudience) {
42872
+ async exchangeCode(code, clientId, redirectUri, codeVerifier, clientSecret, defaultAudience, issuer) {
42611
42873
  if (this.dcrClientRegistryImpl.verifyClientSecret(clientId, clientSecret) === "invalid") {
42612
42874
  this.logger.warn("Client authentication failed at token endpoint (authorization_code)");
42613
42875
  return { error: "invalid_client", error_description: "Client authentication failed" };
@@ -42674,7 +42936,7 @@ var init_instance_local_primary_auth = __esm({
42674
42936
  customClaims: codeRecord.customClaims
42675
42937
  } : void 0;
42676
42938
  const resource = codeRecord.resource ?? defaultAudience;
42677
- const accessToken = await this.signAccessToken(user, codeRecord.scopes, resource, consentMetadata);
42939
+ const accessToken = await this.signAccessToken(user, codeRecord.scopes, resource, consentMetadata, issuer);
42678
42940
  if (codeRecord.pendingAuthId && codeRecord.federatedLoginUsed) {
42679
42941
  try {
42680
42942
  const pendingAuthId = `pending:${codeRecord.pendingAuthId}`;
@@ -42716,7 +42978,7 @@ var init_instance_local_primary_auth = __esm({
42716
42978
  /**
42717
42979
  * Refresh an access token using a refresh token
42718
42980
  */
42719
- async refreshAccessToken(refreshToken, clientId, clientSecret, defaultAudience) {
42981
+ async refreshAccessToken(refreshToken, clientId, clientSecret, defaultAudience, issuer) {
42720
42982
  if (this.dcrClientRegistryImpl.verifyClientSecret(clientId, clientSecret) === "invalid") {
42721
42983
  this.logger.warn("Client authentication failed at token endpoint (refresh_token)");
42722
42984
  return { error: "invalid_client", error_description: "Client authentication failed" };
@@ -42753,7 +43015,7 @@ var init_instance_local_primary_auth = __esm({
42753
43015
  customClaims: tokenRecord.customClaims
42754
43016
  } : void 0;
42755
43017
  const resource = tokenRecord.resource ?? defaultAudience;
42756
- const accessToken = await this.signAccessToken(user, tokenRecord.scopes, resource, consentMetadata);
43018
+ const accessToken = await this.signAccessToken(user, tokenRecord.scopes, resource, consentMetadata, issuer);
42757
43019
  const newRefreshRecord = this.authorizationStore.createRefreshTokenRecord({
42758
43020
  clientId,
42759
43021
  userSub: tokenRecord.userSub,
@@ -45876,12 +46138,12 @@ async function createElicitationStore(options = {}) {
45876
46138
  sqlite,
45877
46139
  keyPrefix = "mcp:elicit:",
45878
46140
  logger,
45879
- isEdgeRuntime: isEdgeRuntime2 = false,
46141
+ isEdgeRuntime: isEdgeRuntime3 = false,
45880
46142
  requiresPubSub = true,
45881
46143
  encryption
45882
46144
  } = options;
45883
46145
  if (sqlite) {
45884
- if (isEdgeRuntime2) {
46146
+ if (isEdgeRuntime3) {
45885
46147
  throw new ElicitationNotSupportedError(
45886
46148
  "SQLite elicitation store is not supported on Edge runtime (better-sqlite3 is a native module). Use Redis or Upstash instead."
45887
46149
  );
@@ -45955,7 +46217,7 @@ async function createElicitationStore(options = {}) {
45955
46217
  "Vercel KV is not supported for elicitation stores. Elicitation requires pub/sub for cross-node result routing, which Vercel KV does not support. Use Redis or Upstash instead."
45956
46218
  );
45957
46219
  }
45958
- if (isEdgeRuntime2 && (!finalStorageConfig || finalStorageConfig.type === "memory")) {
46220
+ if (isEdgeRuntime3 && (!finalStorageConfig || finalStorageConfig.type === "memory")) {
45959
46221
  throw new ElicitationNotSupportedError(
45960
46222
  "Elicitation requires distributed storage when running on Edge runtime. Edge functions are stateless and cannot use in-memory elicitation. Configure Redis or Upstash storage."
45961
46223
  );
@@ -45963,7 +46225,7 @@ async function createElicitationStore(options = {}) {
45963
46225
  const storage = await createStorage2({
45964
46226
  ...finalStorageConfig,
45965
46227
  prefix: keyPrefix,
45966
- fallback: isEdgeRuntime2 ? "error" : "memory"
46228
+ fallback: isEdgeRuntime3 ? "error" : "memory"
45967
46229
  });
45968
46230
  if (requiresPubSub && !storage.supportsPubSub()) {
45969
46231
  throw new ElicitationNotSupportedError(
@@ -47629,6 +47891,7 @@ var WorkflowStepExecutor;
47629
47891
  var init_workflow_step_executor = __esm({
47630
47892
  "libs/sdk/src/workflow/engine/workflow-step.executor.ts"() {
47631
47893
  "use strict";
47894
+ init_call_surface();
47632
47895
  init_errors();
47633
47896
  init_job_errors();
47634
47897
  init_workflow_errors();
@@ -47691,7 +47954,7 @@ var init_workflow_step_executor = __esm({
47691
47954
  const timer = setTimeout(() => {
47692
47955
  reject(new WorkflowJobTimeoutError(job.name, timeout));
47693
47956
  }, timeout);
47694
- Promise.resolve(ctx.execute(parsedInput)).then((result) => {
47957
+ runOnSurface("job", async () => ctx.execute(parsedInput)).then((result) => {
47695
47958
  clearTimeout(timer);
47696
47959
  resolve(result);
47697
47960
  }).catch((err) => {
@@ -47881,12 +48144,13 @@ var init_workflow_engine = __esm({
47881
48144
  });
47882
48145
 
47883
48146
  // libs/sdk/src/job/execution/job-execution.manager.ts
47884
- import { randomUUID as randomUUID23 } from "@frontmcp/utils";
48147
+ import { randomUUID as randomUUID23, runRequestExclusive as runRequestExclusive4 } from "@frontmcp/utils";
47885
48148
  var JobExecutionManager;
47886
48149
  var init_job_execution_manager = __esm({
47887
48150
  "libs/sdk/src/job/execution/job-execution.manager.ts"() {
47888
48151
  "use strict";
47889
48152
  init_principal_utils();
48153
+ init_call_surface();
47890
48154
  init_errors();
47891
48155
  init_workflow_engine();
47892
48156
  init_job_permission_guard();
@@ -47923,7 +48187,7 @@ var init_job_execution_manager = __esm({
47923
48187
  };
47924
48188
  await this.stateStore.createRun(runRecord);
47925
48189
  if (opts.background) {
47926
- this.executeJobBackground(job, input, runId, opts).catch(async (err) => {
48190
+ runRequestExclusive4(() => this.executeJobBackground(job, input, runId, opts)).catch(async (err) => {
47927
48191
  this.logger.error(`Background job execution failed: ${err}`);
47928
48192
  try {
47929
48193
  await this.updateState(runId, {
@@ -47963,18 +48227,20 @@ var init_job_execution_manager = __esm({
47963
48227
  };
47964
48228
  await this.stateStore.createRun(runRecord);
47965
48229
  if (opts.background) {
47966
- this.executeWorkflowBackground(workflow, jobRegistry, runId, opts).catch(async (err) => {
47967
- this.logger.error(`Background workflow execution failed: ${err}`);
47968
- try {
47969
- await this.updateState(runId, {
47970
- state: "failed",
47971
- error: { message: err?.message ?? String(err), name: err?.name ?? "Error" },
47972
- completedAt: Date.now()
47973
- });
47974
- } catch (updateErr) {
47975
- this.logger.error(`Failed to update run state after error: ${updateErr}`);
48230
+ runRequestExclusive4(() => this.executeWorkflowBackground(workflow, jobRegistry, runId, opts)).catch(
48231
+ async (err) => {
48232
+ this.logger.error(`Background workflow execution failed: ${err}`);
48233
+ try {
48234
+ await this.updateState(runId, {
48235
+ state: "failed",
48236
+ error: { message: err?.message ?? String(err), name: err?.name ?? "Error" },
48237
+ completedAt: Date.now()
48238
+ });
48239
+ } catch (updateErr) {
48240
+ this.logger.error(`Failed to update run state after error: ${updateErr}`);
48241
+ }
47976
48242
  }
47977
- });
48243
+ );
47978
48244
  return { runId, state: "pending" };
47979
48245
  }
47980
48246
  return this.executeWorkflowInline(workflow, jobRegistry, runId, opts);
@@ -48027,7 +48293,7 @@ var init_job_execution_manager = __esm({
48027
48293
  authInfo: opts.authInfo ?? {},
48028
48294
  contextProviders: opts.contextProviders
48029
48295
  });
48030
- const result = await ctx.execute(parsedInput);
48296
+ const result = await runOnSurface("job", async () => ctx.execute(parsedInput));
48031
48297
  const logs = ctx.getLogs();
48032
48298
  await this.updateState(runId, {
48033
48299
  state: "completed",
@@ -48262,6 +48528,10 @@ var init_job_interface = __esm({
48262
48528
  getLogs() {
48263
48529
  return this._logs;
48264
48530
  }
48531
+ /** A job (or a workflow step running it) calls tools on the `'job'` surface. */
48532
+ callToolSurface() {
48533
+ return "job";
48534
+ }
48265
48535
  };
48266
48536
  }
48267
48537
  });
@@ -52401,6 +52671,8 @@ var init_server_interface = __esm({
52401
52671
  ServerRequest = class extends IncomingMessage {
52402
52672
  path;
52403
52673
  query;
52674
+ /** Path parameters of the matched route (`:providerId` in `/oauth/provider/:providerId/callback`). */
52675
+ params;
52404
52676
  body;
52405
52677
  authSession;
52406
52678
  };
@@ -52923,9 +53195,10 @@ var init_agent_execution_loop = __esm({
52923
53195
  let iterations = 0;
52924
53196
  let totalPromptTokens = 0;
52925
53197
  let totalCompletionTokens = 0;
53198
+ let timeout;
52926
53199
  try {
52927
53200
  const timeoutPromise = new Promise((_, reject) => {
52928
- setTimeout(
53201
+ timeout = setTimeout(
52929
53202
  () => reject(new Error(`Agent execution timed out after ${this.config.timeout}ms`)),
52930
53203
  this.config.timeout
52931
53204
  );
@@ -52969,6 +53242,8 @@ var init_agent_execution_loop = __esm({
52969
53242
  error,
52970
53243
  durationMs: Date.now() - startTime
52971
53244
  };
53245
+ } finally {
53246
+ clearTimeout(timeout);
52972
53247
  }
52973
53248
  }
52974
53249
  /**
@@ -53288,6 +53563,7 @@ var init_agent_interface = __esm({
53288
53563
  "libs/sdk/src/common/interfaces/agent.interface.ts"() {
53289
53564
  "use strict";
53290
53565
  init_agent_execution_loop();
53566
+ init_frontmcp_context_storage();
53291
53567
  init_elicitation2();
53292
53568
  init_errors();
53293
53569
  init_execution_context_interface();
@@ -53555,6 +53831,10 @@ var init_agent_interface = __esm({
53555
53831
  async invokeAgent(agentId, input) {
53556
53832
  throw new AgentMethodNotAvailableError("invokeAgent", agentId);
53557
53833
  }
53834
+ /** An agent calls tools on the `'agent'` surface, as its model's tool calls do. */
53835
+ callToolSurface() {
53836
+ return "agent";
53837
+ }
53558
53838
  // ============================================================================
53559
53839
  // Notification Methods
53560
53840
  // ============================================================================
@@ -53671,7 +53951,9 @@ var init_agent_interface = __esm({
53671
53951
  {
53672
53952
  sessionId: this.authInfo.sessionId,
53673
53953
  getClientCapabilities: (sid) => this.scope.notifications.getClientCapabilities(sid),
53674
- tryGetContext: () => this.tryGetContext(),
53954
+ // The request holds what answers this question: a fallback answer the owner already sent
53955
+ // (the re-run of its call), or the 2026-07-28 exchange.
53956
+ tryGetContext: () => this.tryGetContext() ?? this.runningRequestContext(),
53675
53957
  entryName: this._agentNameInternal ?? this.agentName,
53676
53958
  entryInput: this._agentInputInternal ?? this.input,
53677
53959
  elicitationEnabled: this.scope.metadata.elicitation?.enabled === true
@@ -53681,6 +53963,17 @@ var init_agent_interface = __esm({
53681
53963
  options
53682
53964
  );
53683
53965
  }
53966
+ /**
53967
+ * The context of the request this agent runs in. An agent is built with its own provider registry,
53968
+ * which holds no request-scoped instances, so the context is read from the running request.
53969
+ */
53970
+ runningRequestContext() {
53971
+ try {
53972
+ return this.scope.providers.get(FrontMcpContextStorage).getStore();
53973
+ } catch {
53974
+ return void 0;
53975
+ }
53976
+ }
53684
53977
  /**
53685
53978
  * Respond with the final output and end execution.
53686
53979
  *
@@ -53836,6 +54129,7 @@ var ChannelContext;
53836
54129
  var init_channel_interface = __esm({
53837
54130
  "libs/sdk/src/common/interfaces/channel.interface.ts"() {
53838
54131
  "use strict";
54132
+ init_call_surface();
53839
54133
  init_mcp_error();
53840
54134
  init_execution_context_interface();
53841
54135
  ChannelContext = class extends ExecutionContextBase {
@@ -53913,13 +54207,27 @@ var init_channel_interface = __esm({
53913
54207
  this.logger.warn(`Channel "${this.channelName}": pushIncoming called but no handler is wired`);
53914
54208
  }
53915
54209
  }
54210
+ /**
54211
+ * While it handles an HTTP trigger (a webhook source's request), a channel calls tools on the
54212
+ * `'http-trigger'` surface. Handling any other event, it dispatches in-process.
54213
+ */
54214
+ callToolSurface() {
54215
+ return getCallSurface() === "http-trigger" ? "http-trigger" : void 0;
54216
+ }
53916
54217
  };
53917
54218
  }
53918
54219
  });
53919
54220
 
53920
54221
  // libs/sdk/src/common/utils/auth-info.utils.ts
53921
- function authInfoFromAuthorization(authorization) {
54222
+ function sessionIdPresentedBy(request) {
54223
+ const header = request.headers?.["mcp-session-id"];
54224
+ if (typeof header === "string" && header.length > 0) return header;
54225
+ const query = request.query?.["sessionId"];
54226
+ return typeof query === "string" && query.length > 0 ? query : void 0;
54227
+ }
54228
+ function authInfoFromAuthorization(authorization, presentedSessionId) {
53922
54229
  const { token, user, session } = authorization;
54230
+ const verifiedSession = session !== void 0 && presentedSessionId !== void 0 && session.id === presentedSessionId ? session : void 0;
53923
54231
  return {
53924
54232
  token,
53925
54233
  clientId: user?.sub,
@@ -53929,8 +54237,8 @@ function authInfoFromAuthorization(authorization) {
53929
54237
  user,
53930
54238
  extra: {
53931
54239
  user,
53932
- sessionId: session?.id,
53933
- sessionPayload: session?.payload
54240
+ sessionId: verifiedSession?.id,
54241
+ sessionPayload: verifiedSession?.payload
53934
54242
  }
53935
54243
  };
53936
54244
  }
@@ -54308,7 +54616,7 @@ var init_decide_request_intent_utils = __esm({
54308
54616
  });
54309
54617
 
54310
54618
  // libs/sdk/src/common/utils/guard.utils.ts
54311
- import { isAnonymousSubject as isAnonymousSubject3 } from "@frontmcp/auth";
54619
+ import { isAnonymousSubject as isAnonymousSubject4 } from "@frontmcp/auth";
54312
54620
  import {
54313
54621
  ConcurrencyLimitError
54314
54622
  } from "@frontmcp/guard";
@@ -54321,15 +54629,19 @@ function enforceIpFilter(scope, clientIp, buildRejection = () => httpRespond.for
54321
54629
  function buildPartitionContext(source) {
54322
54630
  if (!source) return void 0;
54323
54631
  const clientId = source.authInfo?.clientId;
54324
- const userId = isAnonymousSubject3(clientId) ? void 0 : String(clientId);
54325
- const verifiedSessionId = source.authInfo?.sessionId ?? source.authInfo?.extra?.sessionId;
54632
+ const userId = isAnonymousSubject4(clientId) ? void 0 : String(clientId);
54326
54633
  const callerKey = userId ? `user:${userId}` : "anonymous";
54327
54634
  return {
54328
- sessionId: verifiedSessionId === source.sessionId ? source.sessionId : callerKey,
54635
+ sessionId: verifiedSessionOf(source) ?? callerKey,
54329
54636
  clientIp: source.metadata?.clientIp,
54330
54637
  userId
54331
54638
  };
54332
54639
  }
54640
+ function verifiedSessionOf(source) {
54641
+ if ("verifiedSessionId" in source) return source.verifiedSessionId;
54642
+ const confirmed = source.authInfo?.sessionId ?? source.authInfo?.extra?.sessionId;
54643
+ return confirmed === source.sessionId ? source.sessionId : void 0;
54644
+ }
54333
54645
  function partitionsByIdentity(partitionBy) {
54334
54646
  return partitionBy === "session" || partitionBy === "userId" || typeof partitionBy === "function";
54335
54647
  }
@@ -54512,8 +54824,12 @@ var init_primary_auth_provider_interface = __esm({
54512
54824
  * @param _expectedAudience The protected resource(s) the token must be for
54513
54825
  * (`aud` names at least one), when the caller is a
54514
54826
  * 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.
54515
54831
  */
54516
- verifyGatewayToken(_token, _requestBaseUrl, _expectedAudience) {
54832
+ verifyGatewayToken(_token, _requestBaseUrl, _expectedAudience, _expectedIssuer) {
54517
54833
  return Promise.resolve({
54518
54834
  ok: false,
54519
54835
  error: "gateway token verification is not supported by this auth mode"
@@ -55045,7 +55361,10 @@ function buildPerSkillResourceRecord(scope, skill, opts = {}) {
55045
55361
  description: skill.metadata.description,
55046
55362
  mimeType: SKILL_MD_MIME_TYPE,
55047
55363
  annotations,
55048
- _meta: meta
55364
+ _meta: meta,
55365
+ // The skill's own `availableWhen`, so `resources/list` and `resources/read` offer its SKILL.md
55366
+ // on the surfaces that offer the skill, and answer others as for an unknown resource.
55367
+ ...skill.metadata.availableWhen !== void 0 && { availableWhen: skill.metadata.availableWhen }
55049
55368
  };
55050
55369
  const handler = async () => {
55051
55370
  const { loadResult } = await findAndLoadSkillByPath(scope, skillPath);
@@ -55119,6 +55438,8 @@ function refreshFromSkill(resource, skill) {
55119
55438
  }
55120
55439
  Object.assign(resource.metadata, extensions);
55121
55440
  resource.metadata["description"] = skill.metadata.description;
55441
+ if (skill.metadata.availableWhen === void 0) Reflect.deleteProperty(resource.metadata, "availableWhen");
55442
+ else resource.metadata["availableWhen"] = skill.metadata.availableWhen;
55122
55443
  resource.extensionKeys = Object.keys(extensions);
55123
55444
  }
55124
55445
  var init_sep_2640_per_skill = __esm({
@@ -55218,11 +55539,23 @@ var init_skill_audit_helper = __esm({
55218
55539
  });
55219
55540
 
55220
55541
  // libs/sdk/src/skill/skill-scope.helper.ts
55542
+ function declaresDynamicSkills(metadata) {
55543
+ return metadata.dynamicSkills === true || (metadata.plugins ?? []).some((plugin) => declaresDynamicSkills(normalizePlugin(plugin).metadata));
55544
+ }
55545
+ function pluginsRegisterDynamicSkills(plugins) {
55546
+ for (const plugin of plugins) {
55547
+ if (declaresDynamicSkills(normalizePlugin(plugin).metadata)) return true;
55548
+ }
55549
+ return false;
55550
+ }
55221
55551
  async function registerSkillCapabilities(options) {
55222
- const { skillRegistry, flowRegistry, resourceRegistry, providers, skillsConfig, logger } = options;
55552
+ const { skillRegistry, flowRegistry, resourceRegistry, providers, skillsConfig, logger, dynamicSkills } = options;
55223
55553
  registerSkillAuditWriter({ providers, audit: skillsConfig?.audit, logger });
55224
55554
  await flowRegistry.registryFlows([FilterSkillsFlow]);
55225
- if (!skillRegistry.hasAny()) {
55555
+ if (dynamicSkills) {
55556
+ skillRegistry.expectDynamicSkills();
55557
+ }
55558
+ if (!skillRegistry.servesSkills()) {
55226
55559
  return;
55227
55560
  }
55228
55561
  await flowRegistry.registryFlows([SearchSkillsFlow, LoadSkillFlow]);
@@ -55264,6 +55597,7 @@ async function registerSep2640Resources(options) {
55264
55597
  var init_skill_scope_helper = __esm({
55265
55598
  "libs/sdk/src/skill/skill-scope.helper.ts"() {
55266
55599
  "use strict";
55600
+ init_plugin_utils();
55267
55601
  init_flows2();
55268
55602
  init_resources();
55269
55603
  init_sep_2640_last_modified();
@@ -55849,8 +56183,10 @@ var init_session_store_factory = __esm({
55849
56183
  });
55850
56184
 
55851
56185
  // libs/sdk/src/transport/flows/handle.mcp-20260728.flow.ts
56186
+ import { isAnonymousSubject as isAnonymousSubject5 } from "@frontmcp/auth";
55852
56187
  import { z as z102 } from "@frontmcp/lazy-zod";
55853
56188
  import { MCP_20260728_META as MCP_20260728_META5 } from "@frontmcp/protocol";
56189
+ import { runRequestExclusive as runRequestExclusive5 } from "@frontmcp/utils";
55854
56190
  function frame2(message) {
55855
56191
  return encoder3.encode(`event: message
55856
56192
  data: ${JSON.stringify(message)}
@@ -55900,6 +56236,11 @@ async function* streamMessageResponse(options, sink, requestId, runInContext) {
55900
56236
  }
55901
56237
  yield frame2({ jsonrpc: "2.0", id, result: outcome?.result });
55902
56238
  }
56239
+ function isAnonymousCaller(auth) {
56240
+ if (!auth) return true;
56241
+ if (!isAnonymousSubject5(auth.user?.sub)) return false;
56242
+ return !auth.token || auth.session?.payload?.isPublic === true;
56243
+ }
55903
56244
  function errorResponse(status, error, id) {
55904
56245
  return httpRespond.json(
55905
56246
  {
@@ -55956,11 +56297,7 @@ var init_handle_mcp_20260728_flow = __esm({
55956
56297
  async parseInput() {
55957
56298
  const { request } = this.rawInput;
55958
56299
  const auth = request[ServerRequestTokens.auth];
55959
- this.state.set(
55960
- stateSchema37.parse({
55961
- isAnonymous: !auth?.token || auth.token.length === 0 || auth.session?.payload?.isPublic === true
55962
- })
55963
- );
56300
+ this.state.set(stateSchema37.parse({ isAnonymous: isAnonymousCaller(auth) }));
55964
56301
  }
55965
56302
  async validate() {
55966
56303
  const { request } = this.rawInput;
@@ -56017,7 +56354,7 @@ var init_handle_mcp_20260728_flow = __esm({
56017
56354
  if (!context) return (fn) => fn();
56018
56355
  const storage = this.scope.providers.get(FrontMcpContextStorage);
56019
56356
  return async (fn) => {
56020
- await storage.runWithContext(context, fn);
56357
+ await runRequestExclusive5(() => storage.runWithContext(context, fn));
56021
56358
  };
56022
56359
  }
56023
56360
  findToolSchema(scope, toolName) {
@@ -56103,10 +56440,12 @@ var init_handle_mcp_20260728_flow = __esm({
56103
56440
  clientCapabilities,
56104
56441
  frontmcpContext: this.tryGetContext(),
56105
56442
  authInfo: auth ? {
56443
+ // No session is presented: this revision has none, so the request has no verified
56444
+ // session (`extra.sessionId`), and anything kept across requests falls back to the
56445
+ // authenticated caller.
56106
56446
  ...authInfoFromAuthorization(auth),
56107
- // Sessions no longer exist at the protocol level, but the shared
56108
- // handlers key per-request state (memory, credentials) off an id.
56109
- // Derive a request-scoped one so nothing leaks between calls.
56447
+ // 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.
56110
56449
  sessionId: auth.session?.id
56111
56450
  } : void 0,
56112
56451
  isAnonymous: this.state.required.isAnonymous,
@@ -56991,6 +57330,7 @@ var init_handle_streamable_http_flow = __esm({
56991
57330
  init_ext_apps2();
56992
57331
  init_ha_constants();
56993
57332
  init_skill_mode_utils();
57333
+ init_mcp_surface();
56994
57334
  plan40 = {
56995
57335
  pre: ["parseInput", "router"],
56996
57336
  execute: ["onInitialize", "onMessage", "onElicitResult", "onSseListener", "onExtApps"],
@@ -57373,7 +57713,8 @@ var init_handle_streamable_http_flow = __esm({
57373
57713
  sessionId: session.id,
57374
57714
  sessionIdPayload: session.payload,
57375
57715
  token
57376
- }
57716
+ },
57717
+ surface: mcpRequestSurface(this.scope)
57377
57718
  }
57378
57719
  });
57379
57720
  const parsed = CallToolResultSchema4.safeParse(result);
@@ -59695,6 +60036,41 @@ var init_request_log_redaction = __esm({
59695
60036
  }
59696
60037
  });
59697
60038
 
60039
+ // libs/sdk/src/transport/persistent-session-owner.ts
60040
+ var persistent_session_owner_exports = {};
60041
+ __export(persistent_session_owner_exports, {
60042
+ claimPersistentSession: () => claimPersistentSession,
60043
+ persistentSessionCallerKey: () => persistentSessionCallerKey
60044
+ });
60045
+ import { isAnonymousSubject as isAnonymousSubject6 } from "@frontmcp/auth";
60046
+ import { sha256Hex as sha256Hex13 } from "@frontmcp/utils";
60047
+ function persistentSessionCallerKey(authorization) {
60048
+ const sub = authorization?.user?.sub;
60049
+ if (!isAnonymousSubject6(sub)) {
60050
+ const iss = authorization?.user?.iss;
60051
+ return `sub:${sha256Hex13(JSON.stringify([typeof iss === "string" ? iss : "", sub]))}`;
60052
+ }
60053
+ if (authorization?.token) return `token:${sha256Hex13(authorization.token)}`;
60054
+ return null;
60055
+ }
60056
+ function claimPersistentSession(session, callerKey, store) {
60057
+ if (!owners.has(session) && store && store.initial !== void 0) {
60058
+ owners.set(session, store.initial);
60059
+ }
60060
+ if (!owners.has(session)) {
60061
+ owners.set(session, callerKey);
60062
+ return "claimed";
60063
+ }
60064
+ return owners.get(session) === callerKey ? "owner" : "refused";
60065
+ }
60066
+ var owners;
60067
+ var init_persistent_session_owner = __esm({
60068
+ "libs/sdk/src/transport/persistent-session-owner.ts"() {
60069
+ "use strict";
60070
+ owners = /* @__PURE__ */ new WeakMap();
60071
+ }
60072
+ });
60073
+
59698
60074
  // libs/sdk/src/transport/web-standard-mcp.ts
59699
60075
  var web_standard_mcp_exports = {};
59700
60076
  __export(web_standard_mcp_exports, {
@@ -59721,7 +60097,8 @@ async function wireServer(scope, serverOptions, sessionId, sessionIdGenerator, e
59721
60097
  }
59722
60098
  async function buildPersistentWebStandardMcp(scope, options) {
59723
60099
  const serverOptions = buildScopedServerOptions(scope);
59724
- return wireServer(scope, serverOptions, options.sessionId, () => options.sessionId, false);
60100
+ const pair = await wireServer(scope, serverOptions, options.sessionId, () => options.sessionId, false);
60101
+ return options.owner ? { ...pair, owner: options.owner } : pair;
59725
60102
  }
59726
60103
  async function runWebStandardMcp(scope, request, options) {
59727
60104
  if (options.persistent) {
@@ -59730,13 +60107,7 @@ async function runWebStandardMcp(scope, request, options) {
59730
60107
  const sessionId = `web:${randomUUID26()}`;
59731
60108
  const accept = request.headers.get("accept") ?? "";
59732
60109
  const wantsStream = request.method === "GET" || options.sse && accept.includes("text/event-stream");
59733
- const { mcpServer, transport } = await wireServer(
59734
- scope,
59735
- options.serverOptions,
59736
- sessionId,
59737
- void 0,
59738
- !wantsStream
59739
- );
60110
+ const { mcpServer, transport } = await wireServer(scope, options.serverOptions, sessionId, void 0, !wantsStream);
59740
60111
  const response = await transport.handleRequest(request, {
59741
60112
  authInfo: options.authInfo
59742
60113
  });
@@ -59798,6 +60169,10 @@ var init_http_request_flow = __esm({
59798
60169
  "router"
59799
60170
  ],
59800
60171
  execute: [
60172
+ // A Durable Object's persistent session serves only the caller that opened
60173
+ // it: anyone else presenting its id is answered as an unknown session. First,
60174
+ // before every protocol handler (2026-07-28 included), so none can serve it.
60175
+ "checkPersistentSessionOwner",
59801
60176
  // Protocol 2026-07-28. Runs before EVERY session-era handler — including
59802
60177
  // `handleWebFetch`, which answers any request carrying a Web `Request`
59803
60178
  // regardless of intent and would otherwise swallow 2026 traffic on a
@@ -59934,7 +60309,8 @@ var init_http_request_flow = __esm({
59934
60309
  const ctx = this.tryGetContext();
59935
60310
  if (ctx) {
59936
60311
  const { token, user } = result.authorization;
59937
- ctx.updateAuthInfo(authInfoFromAuthorization(result.authorization));
60312
+ const presentedSessionId = this.servesProtocol20260728() ? void 0 : sessionIdPresentedBy(request);
60313
+ ctx.updateAuthInfo(authInfoFromAuthorization(result.authorization, presentedSessionId));
59938
60314
  await this.bindOrchestrationAccessor(ctx, token, user);
59939
60315
  }
59940
60316
  }
@@ -59986,6 +60362,24 @@ var init_http_request_flow = __esm({
59986
60362
  });
59987
60363
  ctx.setContextToken(ORCHESTRATED_AUTH_ACCESSOR2, new OrchestratedAuthAccessorAdapter(authorization));
59988
60364
  }
60365
+ /**
60366
+ * Whether this request is served under MCP protocol 2026-07-28.
60367
+ *
60368
+ * A V8-isolate deployment (Cloudflare Worker) defaults to the stateless revision: it needs no
60369
+ * session storage, so an unversioned call is served directly instead of minting a session in a
60370
+ * Durable Object. Node keeps defaulting to the session pipeline. Either way an explicit declaration
60371
+ * from the client always wins.
60372
+ */
60373
+ servesProtocol20260728() {
60374
+ const { request } = this.rawInput;
60375
+ const isWebMode = !!request[ServerRequestTokens.webRequest];
60376
+ const defaultProtocolVersion = this.scope.metadata.transport?.defaultProtocolVersion ?? (isWebMode ? "2026-07-28" : "legacy");
60377
+ return isProtocol20260728Request({
60378
+ headers: request.headers,
60379
+ body: request.body,
60380
+ defaultVersion: defaultProtocolVersion
60381
+ });
60382
+ }
59989
60383
  async router() {
59990
60384
  try {
59991
60385
  const { request } = this.rawInput;
@@ -60019,12 +60413,7 @@ var init_http_request_flow = __esm({
60019
60413
  reasons: decision.reasons,
60020
60414
  debug: decision.debug
60021
60415
  });
60022
- const defaultProtocolVersion = transportConfig.defaultProtocolVersion ?? (isWebMode ? "2026-07-28" : "legacy");
60023
- if (isProtocol20260728Request({
60024
- headers: request.headers,
60025
- body: request.body,
60026
- defaultVersion: defaultProtocolVersion
60027
- })) {
60416
+ if (this.servesProtocol20260728()) {
60028
60417
  const verify = this.state.required.verifyResult;
60029
60418
  if (verify.kind === "authorized") {
60030
60419
  request[ServerRequestTokens.auth] = verify.authorization;
@@ -60070,6 +60459,11 @@ var init_http_request_flow = __esm({
60070
60459
  const deleteVerify = this.state.required.verifyResult;
60071
60460
  const requestedSessionId = request[ServerRequestTokens.sessionId] ?? request.headers["mcp-session-id"] ?? request.query?.["sessionId"];
60072
60461
  const deleteAuthz = authorizeSessionTermination(deleteVerify, requestedSessionId);
60462
+ if (request[ServerRequestTokens.webTransport] && deleteAuthz.kind !== "unauthorized") {
60463
+ request[ServerRequestTokens.auth] = deleteVerify.authorization;
60464
+ this.state.set("intent", decision.intent);
60465
+ return;
60466
+ }
60073
60467
  if (deleteAuthz.kind === "unauthorized") {
60074
60468
  this.logger.warn(`[${this.requestId}] DELETE session denied: request is not authenticated`);
60075
60469
  this.respond(
@@ -60183,6 +60577,28 @@ var init_http_request_flow = __esm({
60183
60577
  throw error;
60184
60578
  }
60185
60579
  }
60580
+ async checkPersistentSessionOwner() {
60581
+ const req = this.rawInput.request;
60582
+ const persistent = req[ServerRequestTokens.webTransport];
60583
+ if (!req[ServerRequestTokens.webRequest] || !persistent || typeof persistent !== "object") return;
60584
+ const { claimPersistentSession: claimPersistentSession2, persistentSessionCallerKey: persistentSessionCallerKey2 } = await Promise.resolve().then(() => (init_persistent_session_owner(), persistent_session_owner_exports));
60585
+ const authorization = req[ServerRequestTokens.auth];
60586
+ const callerKey = persistentSessionCallerKey2(authorization);
60587
+ const store = persistent.owner;
60588
+ const claim = claimPersistentSession2(persistent, callerKey, store);
60589
+ if (claim === "claimed") {
60590
+ await store?.save(callerKey);
60591
+ return;
60592
+ }
60593
+ if (claim === "owner") return;
60594
+ this.logger.warn(`[${this.requestId}] Persistent session refused: it was opened by another caller`);
60595
+ this.respond(
60596
+ httpRespond.json(
60597
+ { jsonrpc: "2.0", error: { code: -32001, message: "Session not found" }, id: null },
60598
+ { status: 404 }
60599
+ )
60600
+ );
60601
+ }
60186
60602
  async handleWebFetch() {
60187
60603
  const { request } = this.rawInput;
60188
60604
  const req = request;
@@ -60197,7 +60613,7 @@ var init_http_request_flow = __esm({
60197
60613
  const proto = expandProtocolConfig2(scope.metadata.transport?.protocol);
60198
60614
  const sse = proto.streamable && !proto.json;
60199
60615
  const authorization = req[ServerRequestTokens.auth];
60200
- const authInfo = authorization ? authInfoFromAuthorization(authorization) : void 0;
60616
+ const authInfo = authorization ? authInfoFromAuthorization(authorization, sessionIdPresentedBy(request)) : void 0;
60201
60617
  const persistent = req[ServerRequestTokens.webTransport];
60202
60618
  const response = await runWebStandardMcp2(scope, webRequest, {
60203
60619
  serverOptions: buildScopedServerOptions2(scope),
@@ -60403,6 +60819,9 @@ var init_http_request_flow = __esm({
60403
60819
  __decorateClass([
60404
60820
  Stage43("router")
60405
60821
  ], HttpRequestFlow.prototype, "router", 1);
60822
+ __decorateClass([
60823
+ Stage43("checkPersistentSessionOwner")
60824
+ ], HttpRequestFlow.prototype, "checkPersistentSessionOwner", 1);
60406
60825
  __decorateClass([
60407
60826
  Stage43("handleWebFetch")
60408
60827
  ], HttpRequestFlow.prototype, "handleWebFetch", 1);
@@ -60990,7 +61409,11 @@ var init_scope_instance = __esm({
60990
61409
  if (!this.cliMode) {
60991
61410
  await this.buildAuthUiRegistry();
60992
61411
  }
60993
- if (this.scopeSkills.hasAny()) {
61412
+ const dynamicSkills = pluginsRegisterDynamicSkills([
61413
+ ...serverPlugins,
61414
+ ...this.scopeApps.getApps().flatMap((app) => "plugins" in app.metadata ? app.metadata.plugins ?? [] : [])
61415
+ ]);
61416
+ if (this.scopeSkills.hasAny() || dynamicSkills) {
60994
61417
  const store = createSkillSessionStore({ type: "memory" });
60995
61418
  this._skillSession = new SkillSessionManager(
60996
61419
  {
@@ -61041,7 +61464,8 @@ var init_scope_instance = __esm({
61041
61464
  resourceRegistry: this.scopeResources,
61042
61465
  providers: this.scopeProviders,
61043
61466
  skillsConfig: this.metadata.skillsConfig,
61044
- logger: this.logger
61467
+ logger: this.logger,
61468
+ dynamicSkills
61045
61469
  });
61046
61470
  const jobsConfig = this.metadata.jobs;
61047
61471
  const metaRecord = this.metadata;
@@ -61844,7 +62268,7 @@ import {
61844
62268
  ProviderScope as ProviderScope8,
61845
62269
  tokenName as tokenName18
61846
62270
  } from "@frontmcp/di";
61847
- function verifiedSessionOf(requestContext) {
62271
+ function verifiedSessionOf2(requestContext) {
61848
62272
  const { authInfo } = requestContext;
61849
62273
  const verified = authInfo.sessionId ?? authInfo.extra?.["sessionId"];
61850
62274
  return typeof verified === "string" && verified.length > 0 && verified !== STATELESS_SESSION_ID ? verified : void 0;
@@ -62605,7 +63029,7 @@ var init_provider_registry = __esm({
62605
63029
  if (!(requestContext instanceof FrontMcpContext)) {
62606
63030
  return newProviderStore();
62607
63031
  }
62608
- if (!this.sessionCacheEnabled || verifiedSessionOf(requestContext) !== sessionKey) {
63032
+ if (!this.sessionCacheEnabled || verifiedSessionOf2(requestContext) !== sessionKey) {
62609
63033
  let stores = requestContext.get(REQUEST_PROVIDER_STORES);
62610
63034
  if (!stores) {
62611
63035
  stores = /* @__PURE__ */ new WeakMap();
@@ -63027,6 +63451,7 @@ var init_web_response_renderer = __esm({
63027
63451
  });
63028
63452
 
63029
63453
  // libs/sdk/src/transport/web-fetch-handler.ts
63454
+ import { runRequestExclusive as runRequestExclusive6 } from "@frontmcp/utils";
63030
63455
  function isCloudflareWorkersRuntime(request) {
63031
63456
  if (typeof navigator !== "undefined" && navigator.userAgent === CLOUDFLARE_WORKERS_USER_AGENT) return true;
63032
63457
  const cloudflareProperties = request.cf;
@@ -63140,10 +63565,12 @@ async function runHttpRequestFlowWeb(scope, request, opts = {}) {
63140
63565
  const serverRequest = await toServerRequest(request, url, opts.ctx, opts.persistent, opts.env);
63141
63566
  let output;
63142
63567
  try {
63143
- output = await scope.runFlow("http:request", {
63144
- request: serverRequest,
63145
- response: {}
63146
- });
63568
+ output = await runRequestExclusive6(
63569
+ () => scope.runFlow("http:request", {
63570
+ request: serverRequest,
63571
+ response: {}
63572
+ })
63573
+ );
63147
63574
  } catch (error) {
63148
63575
  output = flowErrorToHttpOutput(error);
63149
63576
  }
@@ -63156,10 +63583,12 @@ async function runMatchingHttpFlowWeb(scope, request, opts = {}) {
63156
63583
  if (!flowName) return void 0;
63157
63584
  let output;
63158
63585
  try {
63159
- output = await scope.runFlow(flowName, {
63160
- request: serverRequest,
63161
- response: {}
63162
- });
63586
+ output = await runRequestExclusive6(
63587
+ () => scope.runFlow(flowName, {
63588
+ request: serverRequest,
63589
+ response: {}
63590
+ })
63591
+ );
63163
63592
  } catch (error) {
63164
63593
  output = flowErrorToHttpOutput(error);
63165
63594
  }
@@ -63170,6 +63599,7 @@ async function toServerRequest(request, url, ctx, persistent, env) {
63170
63599
  request.headers.forEach((v, k) => {
63171
63600
  headers[k] = v;
63172
63601
  });
63602
+ headers["host"] = url.host;
63173
63603
  const query = {};
63174
63604
  url.searchParams.forEach((v, k) => {
63175
63605
  const existing = query[k];
@@ -63200,6 +63630,8 @@ async function toServerRequest(request, url, ctx, persistent, env) {
63200
63630
  const webRequest = new Request(url.toString(), init);
63201
63631
  const serverRequest = {
63202
63632
  method,
63633
+ // The scheme the client used, as Express reports it (`req.protocol`).
63634
+ protocol: url.protocol.slice(0, -1),
63203
63635
  path: url.pathname,
63204
63636
  url: url.pathname + url.search,
63205
63637
  headers,
@@ -63280,7 +63712,10 @@ var init_web_fetch_handler = __esm({
63280
63712
  MISCONFIGURATION_REMEDIES = {
63281
63713
  SESSION_SECRET_REQUIRED: "Set MCP_SESSION_SECRET in the deployment environment (e.g. `wrangler secret put MCP_SESSION_SECRET`). Session IDs are encrypted with it, and production refuses the development machine-id fallback.",
63282
63714
  JWT_SECRET_INVALID: "JWT_SECRET is present but too weak: HS256 requires at least 32 bytes (RFC 7518). Replace it with `openssl rand -hex 32`.",
63283
- JWT_SECRET_REQUIRED: "Set JWT_SECRET in the deployment environment (e.g. `wrangler secret put JWT_SECRET`). Tokens are signed with it; production refuses the random per-process fallback because tokens would not survive a restart or verify across instances."
63715
+ JWT_SECRET_REQUIRED: "Set JWT_SECRET in the deployment environment (e.g. `wrangler secret put JWT_SECRET`). Tokens are signed with it; production refuses the random per-process fallback because tokens would not survive a restart or verify across instances.",
63716
+ // The startup checks: a server whose entries ask for protection nothing gives them does not start.
63717
+ 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
+ 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."
63284
63719
  };
63285
63720
  }
63286
63721
  });
@@ -63930,12 +64365,172 @@ var init_front_mcp_providers = __esm({
63930
64365
  }
63931
64366
  });
63932
64367
 
64368
+ // libs/sdk/src/front-mcp/static-startup.check.ts
64369
+ function tryNormalize(normalize) {
64370
+ try {
64371
+ return normalize();
64372
+ } catch {
64373
+ return void 0;
64374
+ }
64375
+ }
64376
+ function asMetadata(value) {
64377
+ return value !== null && typeof value === "object" ? value : void 0;
64378
+ }
64379
+ function isServedEverywhere(metadata) {
64380
+ const availability = asMetadata(metadata["availableWhen"]);
64381
+ if (!availability) return true;
64382
+ return Object.entries(availability).every(
64383
+ ([axis, value]) => value === void 0 || axis === "surface" && Array.isArray(value) && value.length > 0
64384
+ );
64385
+ }
64386
+ function pluginClassOf(record) {
64387
+ switch (record.kind) {
64388
+ case "CLASS_TOKEN" /* CLASS_TOKEN */:
64389
+ return record.provide;
64390
+ case "CLASS" /* CLASS */:
64391
+ return record.useClass;
64392
+ case "VALUE" /* VALUE */:
64393
+ return record.useValue?.constructor;
64394
+ default:
64395
+ return void 0;
64396
+ }
64397
+ }
64398
+ function pluginHooksOf(record) {
64399
+ const pluginClass = pluginClassOf(record);
64400
+ if (!pluginClass) return void 0;
64401
+ return [...collectHook(pluginClass), ...peekPendingTC39HooksForClass(pluginClass)];
64402
+ }
64403
+ function keysEnforcedByPlugins2(plugins, into = /* @__PURE__ */ new Set()) {
64404
+ for (const plugin of plugins ?? []) {
64405
+ const record = tryNormalize(() => normalizePlugin(plugin));
64406
+ if (!record) continue;
64407
+ for (const key of record.metadata.enforcesMetadata ?? []) into.add(key);
64408
+ keysEnforcedByPlugins2(record.metadata.plugins, into);
64409
+ }
64410
+ return into;
64411
+ }
64412
+ function collectStaticEntries(config) {
64413
+ const found = [];
64414
+ const everyAppKeys = /* @__PURE__ */ new Set();
64415
+ const ownAppKeys = /* @__PURE__ */ new Map();
64416
+ const add = (label, metadata, reach) => {
64417
+ const fields = asMetadata(metadata);
64418
+ if (!fields || !isServedEverywhere(fields)) return false;
64419
+ found.push({ label, metadata: fields, reach });
64420
+ return true;
64421
+ };
64422
+ const keysOfApp = (app) => {
64423
+ const keys = ownAppKeys.get(app) ?? /* @__PURE__ */ new Set();
64424
+ ownAppKeys.set(app, keys);
64425
+ return keys;
64426
+ };
64427
+ const visitPlugins = (plugins, app) => {
64428
+ for (const plugin of plugins ?? []) {
64429
+ const record = tryNormalize(() => normalizePlugin(plugin));
64430
+ if (!record) continue;
64431
+ const hooks = pluginHooksOf(record);
64432
+ if (hooks === void 0 || hooks.length > 0) {
64433
+ const gatesOnlyItsApp = hooks !== void 0 && !hooks.some((hook) => hook.appliesTo === "uncovered-apps");
64434
+ const keys = app && gatesOnlyItsApp ? keysOfApp(app) : everyAppKeys;
64435
+ for (const key of record.metadata.enforcesMetadata ?? []) keys.add(key);
64436
+ }
64437
+ visitEntries(record.metadata, app);
64438
+ }
64439
+ };
64440
+ const visitEntries = (owner, app) => {
64441
+ const reach = app ? { app } : "every-plugin";
64442
+ for (const item of owner.tools ?? []) {
64443
+ const record = tryNormalize(() => normalizeTool(item));
64444
+ if (record) add(`Tool "${record.metadata.id || record.metadata.name}"`, record.metadata, reach);
64445
+ }
64446
+ for (const item of owner.resources ?? []) {
64447
+ if (tryNormalize(() => isResourceTemplate(item))) {
64448
+ const record = tryNormalize(() => normalizeResourceTemplate(item));
64449
+ if (record) add(`Resource template "${record.metadata.name}"`, record.metadata, reach);
64450
+ } else {
64451
+ const record = tryNormalize(() => normalizeResource(item));
64452
+ if (record) add(`Resource "${record.metadata.name}"`, record.metadata, reach);
64453
+ }
64454
+ }
64455
+ for (const item of owner.prompts ?? []) {
64456
+ const record = tryNormalize(() => normalizePrompt(item));
64457
+ if (record) add(`Prompt "${record.metadata.name}"`, record.metadata, reach);
64458
+ }
64459
+ for (const item of owner.agents ?? []) {
64460
+ const record = tryNormalize(() => normalizeAgent(item));
64461
+ if (!record) continue;
64462
+ const agentName = record.metadata.id ?? record.metadata.name;
64463
+ if (!add(`Agent "${agentName}"`, record.metadata, reach)) continue;
64464
+ const agentKeys = record.metadata.execution?.useToolFlow === false ? /* @__PURE__ */ new Set() : keysEnforcedByPlugins2(record.metadata.plugins);
64465
+ for (const toolItem of record.metadata.tools ?? []) {
64466
+ const toolRecord = tryNormalize(() => normalizeTool(toolItem));
64467
+ if (toolRecord) add(`Tool "${agentName}:${toolRecord.metadata.name}"`, toolRecord.metadata, { agentKeys });
64468
+ }
64469
+ }
64470
+ for (const item of owner.skills ?? []) {
64471
+ const record = tryNormalize(() => normalizeSkill(item));
64472
+ if (record) add(`Skill "${record.metadata.id ?? record.metadata.name}"`, record.metadata, "every-plugin");
64473
+ }
64474
+ visitPlugins(owner.plugins, app);
64475
+ };
64476
+ const server = config;
64477
+ visitEntries({ skills: server.skills, plugins: server.plugins }, void 0);
64478
+ for (const app of server.apps ?? []) {
64479
+ const record = tryNormalize(() => normalizeApp(app));
64480
+ if (record?.kind === "LOCAL_CLASS" /* LOCAL_CLASS */) visitEntries(record.metadata, record);
64481
+ }
64482
+ const everyPluginKeys = /* @__PURE__ */ new Set([...everyAppKeys, ...[...ownAppKeys.values()].flatMap((keys) => [...keys])]);
64483
+ const keysReaching = (reach) => {
64484
+ if (reach === "every-plugin") return everyPluginKeys;
64485
+ if ("agentKeys" in reach) return reach.agentKeys;
64486
+ return /* @__PURE__ */ new Set([...everyAppKeys, ...ownAppKeys.get(reach.app) ?? []]);
64487
+ };
64488
+ return found.map(({ label, metadata, reach }) => ({ label, metadata, enforcedBy: keysReaching(reach) }));
64489
+ }
64490
+ function assertStaticStartupConfig(config) {
64491
+ const entries = collectStaticEntries(config);
64492
+ if (!config.authorities) {
64493
+ const labels = entries.filter(({ metadata }) => isEnforcementRequested(metadata["authorities"])).map(({ label }) => label);
64494
+ if (labels.length > 0) {
64495
+ const names = labels.slice(0, 5).join(", ");
64496
+ const suffix = labels.length > 5 ? ` and ${labels.length - 5} more` : "";
64497
+ throw new AuthConfigurationError(
64498
+ `Authorities configuration required: ${names}${suffix} declare 'authorities' metadata but authorities enforcement is not fully configured (engine/context builder missing). Add 'authorities: { claimsMapping: {...}, profiles: {...} }' to your @FrontMcp() decorator to enable enforcement, or remove 'authorities' from entry metadata.`,
64499
+ { suggestion: "Add authorities config to @FrontMcp() or remove authorities from entry metadata" }
64500
+ );
64501
+ }
64502
+ }
64503
+ const problems = entries.flatMap(
64504
+ ({ label, metadata, enforcedBy }) => getEnforcedMetadataKeys().filter((key) => isEnforcementRequested(metadata[key]) && !enforcedBy.has(key)).map((key) => {
64505
+ const enforcer = describeMetadataEnforcer(key);
64506
+ return `${label} declares '${key}'${enforcer ? ` (enforced by ${enforcer})` : ""}`;
64507
+ })
64508
+ );
64509
+ if (problems.length > 0) throw new UnenforcedMetadataError(problems);
64510
+ }
64511
+ var init_static_startup_check = __esm({
64512
+ "libs/sdk/src/front-mcp/static-startup.check.ts"() {
64513
+ "use strict";
64514
+ init_agent_utils();
64515
+ init_app_utils();
64516
+ init_hook_decorator();
64517
+ init_enforced_metadata_utils();
64518
+ init_errors();
64519
+ init_hooks_utils();
64520
+ init_plugin_utils();
64521
+ init_prompt_utils();
64522
+ init_resource_utils();
64523
+ init_skill_utils();
64524
+ init_tool_utils();
64525
+ }
64526
+ });
64527
+
63933
64528
  // libs/sdk/src/front-mcp/front-mcp.ts
63934
64529
  var front_mcp_exports = {};
63935
64530
  __export(front_mcp_exports, {
63936
64531
  FrontMcpInstance: () => FrontMcpInstance
63937
64532
  });
63938
- import { fileExists as fileExists7, randomUUID as randomUUID28, unlink as unlink2 } from "@frontmcp/utils";
64533
+ import { fileExists as fileExists7, isEdgeRuntime as isEdgeRuntime2, randomUUID as randomUUID28, unlink as unlink2 } from "@frontmcp/utils";
63939
64534
  function resolveConfigInput(optionsOrClass) {
63940
64535
  if (typeof optionsOrClass === "function") {
63941
64536
  const stored = getDecoratorConfig(optionsOrClass);
@@ -63963,6 +64558,7 @@ var init_front_mcp = __esm({
63963
64558
  init_task();
63964
64559
  init_web_fetch_handler();
63965
64560
  init_front_mcp_providers();
64561
+ init_static_startup_check();
63966
64562
  stdioServing = false;
63967
64563
  FrontMcpInstance = class _FrontMcpInstance2 {
63968
64564
  config;
@@ -64121,17 +64717,25 @@ var init_front_mcp = __esm({
64121
64717
  * Express / Node `req`/`res` and runs on V8-isolate targets (Cloudflare
64122
64718
  * Workers, Deno Deploy, Bun).
64123
64719
  *
64720
+ * A server the startup checks refuse (an entry whose `approval`, `featureFlag`
64721
+ * or `authorities` nothing enforces, a missing secret, ...) rejects here, as
64722
+ * with {@link createDirect}. On an edge isolate the server is built on its
64723
+ * first request instead (see below).
64724
+ *
64124
64725
  * @example
64125
64726
  * // worker entry
64126
64727
  * const handler = await FrontMcpInstance.createFetchHandler(config);
64127
64728
  * export default { fetch: (request) => handler(request) };
64128
64729
  */
64129
64730
  static async createFetchHandler(options) {
64130
- let inner;
64131
- let building;
64132
- const build = async () => {
64731
+ const build = async (deferred) => {
64133
64732
  const frontMcp = new _FrontMcpInstance2(frontMcpMetadataSchema.parse(options));
64134
- await frontMcp.ready;
64733
+ try {
64734
+ await frontMcp.ready;
64735
+ } catch (err) {
64736
+ if (deferred) frontMcp.log?.error("FrontMCP failed to start; requests are refused until it does", err);
64737
+ throw err;
64738
+ }
64135
64739
  const scope = frontMcp.getPrimaryScope();
64136
64740
  if (!scope) {
64137
64741
  throw new ServerNotFoundError();
@@ -64139,10 +64743,14 @@ var init_front_mcp = __esm({
64139
64743
  frontMcp.log?.info("FrontMCP fetch handler created (web-standard transport)");
64140
64744
  return createWebFetchHandler(scope);
64141
64745
  };
64746
+ if (!isEdgeRuntime2()) return build(false);
64747
+ assertStaticStartupConfig(options);
64748
+ let inner;
64749
+ let building;
64142
64750
  return async (request, ctx, env) => {
64143
64751
  if (!inner) {
64144
64752
  if (!building) {
64145
- building = build().catch((err) => {
64753
+ building = build(true).catch((err) => {
64146
64754
  building = void 0;
64147
64755
  throw err;
64148
64756
  });
@@ -64649,7 +65257,8 @@ async function executeTaskWorker(options, taskId) {
64649
65257
  await runTaskInBackground({
64650
65258
  record,
64651
65259
  cleanedRequestParams: record.request.params,
64652
- ctx: { authInfo: { sessionId } },
65260
+ // On the surface of the call that created the task, as the in-process runner runs it.
65261
+ ctx: { authInfo: { sessionId }, ...record.surface !== void 0 && { surface: record.surface } },
64653
65262
  scope,
64654
65263
  store: taskStore,
64655
65264
  registry,
@@ -66519,6 +67128,7 @@ var init_common = __esm({
66519
67128
  // libs/sdk/src/index.ts
66520
67129
  init_common();
66521
67130
  init_front_mcp2();
67131
+ init_static_startup_check();
66522
67132
  init_serverless_handler();
66523
67133
  init_common();
66524
67134
  init_errors();
@@ -66856,6 +67466,39 @@ init_agent_decorator();
66856
67466
 
66857
67467
  // libs/sdk/src/index.ts
66858
67468
  init_adapters();
67469
+
67470
+ // libs/sdk/src/hooks/hook-coverage.ts
67471
+ init_lineage_utils();
67472
+ function gateOf(scope, entry) {
67473
+ if ("tool" in entry) {
67474
+ return {
67475
+ flow: "tools:call-tool",
67476
+ ownerId: hookOwnerIdOf(scope.tools.lineageOf(entry.tool) ?? [], entry.tool.owner)
67477
+ };
67478
+ }
67479
+ if ("resource" in entry) {
67480
+ return {
67481
+ flow: "resources:read-resource",
67482
+ ownerId: appOwnerIdOf(scope.resources.lineageOf(entry.resource) ?? [], entry.resource.owner)
67483
+ };
67484
+ }
67485
+ if ("prompt" in entry) {
67486
+ return {
67487
+ flow: "prompts:get-prompt",
67488
+ ownerId: appOwnerIdOf(scope.prompts.lineageOf(entry.prompt) ?? [], entry.prompt.owner)
67489
+ };
67490
+ }
67491
+ return {
67492
+ flow: "skills:filter",
67493
+ ownerId: appOwnerIdOf(scope.skills.lineageOf?.(entry.skill) ?? [], entry.skill.owner)
67494
+ };
67495
+ }
67496
+ function isEntryGatedBy(scope, entry, target) {
67497
+ const { flow, ownerId } = gateOf(scope, entry);
67498
+ return scope.hooks.getFlowHooksForOwner(flow, ownerId).some((hook) => hook.metadata.target === target);
67499
+ }
67500
+
67501
+ // libs/sdk/src/index.ts
66859
67502
  init_config();
66860
67503
  init_config();
66861
67504
  init_direct();
@@ -66876,6 +67519,7 @@ init_mcp_stateless_client_adapter();
66876
67519
  init_ext_apps2();
66877
67520
  import { PROTOCOL_2026_07_28 as PROTOCOL_2026_07_284 } from "@frontmcp/protocol";
66878
67521
  import { InMemoryOrchestratedTokenStore as InMemoryOrchestratedTokenStore2 } from "@frontmcp/auth";
67522
+ import { isAnonymousSubject as isAnonymousSubject7 } from "@frontmcp/auth";
66879
67523
  (function suppressZodExpressWarning() {
66880
67524
  if (typeof process === "undefined" || typeof process.emitWarning !== "function") return;
66881
67525
  const originalEmitWarning = process.emitWarning.bind(process);
@@ -67270,6 +67914,7 @@ export {
67270
67914
  SessionClaimConflictError,
67271
67915
  SessionHookStage,
67272
67916
  SessionIdEmptyError,
67917
+ SessionIdentityRequiredError,
67273
67918
  SessionMissingError,
67274
67919
  SessionSecretRequiredError,
67275
67920
  SessionVerificationFailedError,
@@ -67397,6 +68042,7 @@ export {
67397
68042
  appAuthOptionsSchema,
67398
68043
  appFilterConfigSchema,
67399
68044
  applyPrimitiveFilter,
68045
+ assertStaticStartupConfig,
67400
68046
  authInfoFromAuthorization,
67401
68047
  authOptionsSchema,
67402
68048
  authenticateFnSchema,
@@ -67568,6 +68214,7 @@ export {
67568
68214
  incrementalAuthConfigSchema,
67569
68215
  intentSchema,
67570
68216
  isAlive,
68217
+ isAnonymousSubject7 as isAnonymousSubject,
67571
68218
  isAuthError,
67572
68219
  isChannelClass,
67573
68220
  isChannelFunction,
@@ -67577,6 +68224,7 @@ export {
67577
68224
  isDynamicPluginClass,
67578
68225
  isElicitationEncryptionAvailable,
67579
68226
  isEncryptedBlob,
68227
+ isEntryGatedBy,
67580
68228
  isFileInstructions,
67581
68229
  isInlineInstructions,
67582
68230
  isLazy,
@@ -67657,6 +68305,7 @@ export {
67657
68305
  parseTraceContext,
67658
68306
  partitionsByIdentity,
67659
68307
  pathToEnvKey,
68308
+ peekPendingTC39HooksForClass,
67660
68309
  performElicit,
67661
68310
  performListRoots,
67662
68311
  performSample,
@@ -67702,6 +68351,7 @@ export {
67702
68351
  serializeHttpCookie,
67703
68352
  serverInfoOptionsSchema,
67704
68353
  sessionIdPayloadSchema2 as sessionIdPayloadSchema,
68354
+ sessionIdPresentedBy,
67705
68355
  sessionIdSchema,
67706
68356
  sessionOptionsSchema,
67707
68357
  setNestedValue,