@frontmcp/sdk 1.7.2 → 1.8.0

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 (46) hide show
  1. package/agent/agent.scope.d.ts +12 -0
  2. package/agent/agent.scope.d.ts.map +1 -1
  3. package/auth/auth.registry.d.ts.map +1 -1
  4. package/auth/flows/oauth.authorize.flow.d.ts.map +1 -1
  5. package/auth/flows/session.verify.flow.d.ts +19 -1
  6. package/auth/flows/session.verify.flow.d.ts.map +1 -1
  7. package/auth/instances/instance.local-primary-auth.d.ts +2 -2
  8. package/auth/instances/instance.local-primary-auth.d.ts.map +1 -1
  9. package/auth/session/utils/session-id.utils.d.ts +11 -4
  10. package/auth/session/utils/session-id.utils.d.ts.map +1 -1
  11. package/common/metadata/app.metadata.d.ts +24 -0
  12. package/common/metadata/app.metadata.d.ts.map +1 -1
  13. package/common/metadata/front-mcp.metadata.d.ts +105 -0
  14. package/common/metadata/front-mcp.metadata.d.ts.map +1 -1
  15. package/common/tokens/server.tokens.d.ts +1 -0
  16. package/common/tokens/server.tokens.d.ts.map +1 -1
  17. package/common/types/options/auth/index.d.ts +4 -2
  18. package/common/types/options/auth/index.d.ts.map +1 -1
  19. package/common/utils/path.utils.d.ts +29 -0
  20. package/common/utils/path.utils.d.ts.map +1 -1
  21. package/context/frontmcp-context-storage.d.ts +3 -1
  22. package/context/frontmcp-context-storage.d.ts.map +1 -1
  23. package/context/metadata.utils.d.ts +29 -6
  24. package/context/metadata.utils.d.ts.map +1 -1
  25. package/errors/auth-internal.errors.d.ts +1 -1
  26. package/errors/auth-internal.errors.d.ts.map +1 -1
  27. package/errors/index.d.ts +1 -1
  28. package/errors/index.d.ts.map +1 -1
  29. package/esm/index.mjs +468 -170
  30. package/flows/flow.instance.d.ts.map +1 -1
  31. package/front-mcp/front-mcp.d.ts.map +1 -1
  32. package/front-mcp/front-mcp.providers.d.ts +52 -0
  33. package/front-mcp/front-mcp.providers.d.ts.map +1 -1
  34. package/index.js +882 -581
  35. package/job/execution/job-execution.manager.d.ts.map +1 -1
  36. package/package.json +10 -10
  37. package/scope/flows/http.request.flow.d.ts.map +1 -1
  38. package/scope/scope.instance.d.ts.map +1 -1
  39. package/server/adapters/express.host.adapter.d.ts.map +1 -1
  40. package/tool/ui/ui-shared.d.ts.map +1 -1
  41. package/transport/web-fetch-handler.d.ts +22 -1
  42. package/transport/web-fetch-handler.d.ts.map +1 -1
  43. package/workflow/engine/workflow-step.executor.d.ts +12 -4
  44. package/workflow/engine/workflow-step.executor.d.ts.map +1 -1
  45. package/workflow/engine/workflow.engine.d.ts +2 -4
  46. package/workflow/engine/workflow.engine.d.ts.map +1 -1
package/esm/index.mjs CHANGED
@@ -679,6 +679,7 @@ var init_server_tokens = __esm({
679
679
  reinitialize: tokenFactory.meta("reinitialize"),
680
680
  webRequest: tokenFactory.meta("webRequest"),
681
681
  webCtx: tokenFactory.meta("webCtx"),
682
+ webEnv: tokenFactory.meta("webEnv"),
682
683
  webTransport: tokenFactory.meta("webTransport")
683
684
  };
684
685
  }
@@ -1938,6 +1939,7 @@ import {
1938
1939
  localDcrConfigSchema
1939
1940
  } from "@frontmcp/auth";
1940
1941
  import { publicAuthOptionsSchema } from "@frontmcp/auth";
1942
+ import { staticAuthOptionsSchema } from "@frontmcp/auth";
1941
1943
  import { transparentAuthOptionsSchema } from "@frontmcp/auth";
1942
1944
  import {
1943
1945
  localAuthSchema,
@@ -1952,6 +1954,7 @@ import { appAuthOptionsSchema } from "@frontmcp/auth";
1952
1954
  import {
1953
1955
  parseAuthOptions,
1954
1956
  isPublicMode,
1957
+ isStaticMode,
1955
1958
  isTransparentMode,
1956
1959
  isLocalMode,
1957
1960
  isRemoteMode,
@@ -3152,6 +3155,10 @@ var init_front_mcp_metadata = __esm({
3152
3155
  output: outputPolicySchema.optional(),
3153
3156
  jobs: z24.object({
3154
3157
  enabled: z24.boolean(),
3158
+ // Opt-in for `register_job` / `register_workflow` (GHSA-58v2-gpcc-jmqv).
3159
+ // Omitting it here would let Zod strip the flag before the scope reads
3160
+ // it, leaving the documented escape hatch permanently off.
3161
+ allowDynamicRegistration: z24.boolean().optional(),
3155
3162
  store: z24.object({
3156
3163
  redis: redisOptionsSchema.optional(),
3157
3164
  keyPrefix: z24.string().optional()
@@ -5351,6 +5358,8 @@ import {
5351
5358
  TokenStoreRequiredError,
5352
5359
  NoProviderIdError,
5353
5360
  TokenLeakDetectedError,
5361
+ JwtSecretRequiredError,
5362
+ JwtSecretWeakError,
5354
5363
  SessionSecretRequiredError,
5355
5364
  CredentialProviderAlreadyRegisteredError,
5356
5365
  AuthProvidersNotConfiguredError,
@@ -5740,6 +5749,8 @@ __export(errors_exports, {
5740
5749
  InvalidUseValueError: () => InvalidUseValueError,
5741
5750
  InvokeStateMissingKeyError: () => InvokeStateMissingKeyError,
5742
5751
  JobNotAuthorizedError: () => JobNotAuthorizedError,
5752
+ JwtSecretRequiredError: () => JwtSecretRequiredError,
5753
+ JwtSecretWeakError: () => JwtSecretWeakError,
5743
5754
  MCP_ERROR_CODES: () => MCP_ERROR_CODES,
5744
5755
  McpError: () => McpError,
5745
5756
  MethodNotImplementedError: () => MethodNotImplementedError,
@@ -9349,22 +9360,24 @@ function hasValidSessionStructure(dec) {
9349
9360
  function isValidSessionPayload(dec, sig) {
9350
9361
  return hasValidSessionStructure(dec) && dec.authSig === sig;
9351
9362
  }
9352
- function isValidPublicSessionPayload(dec) {
9363
+ function isValidPublicSessionPayload(dec, expectedAuthSig = "public") {
9353
9364
  if (typeof dec !== "object" || dec === null) return false;
9354
9365
  const d = dec;
9355
- return typeof d["nodeId"] === "string" && d["authSig"] === "public" && typeof d["uuid"] === "string" && typeof d["iat"] === "number" && d["isPublic"] === true;
9366
+ return typeof d["nodeId"] === "string" && d["authSig"] === expectedAuthSig && typeof d["uuid"] === "string" && typeof d["iat"] === "number" && // `isPublic` is set only for public mode, so it must track the signature —
9367
+ // a static-mode session must never decrypt as a public one.
9368
+ d["isPublic"] === (expectedAuthSig === "public");
9356
9369
  }
9357
9370
  function decryptSessionId(sessionId, sig) {
9358
9371
  const dec = safeDecrypt(sessionId);
9359
9372
  return isValidSessionPayload(dec, sig) ? dec : null;
9360
9373
  }
9361
- function decryptPublicSession(sessionId) {
9374
+ function decryptPublicSession(sessionId, expectedAuthSig = "public") {
9362
9375
  const cached = cache.get(sessionId);
9363
- if (cached && isValidPublicSessionPayload(cached)) {
9376
+ if (cached && isValidPublicSessionPayload(cached, expectedAuthSig)) {
9364
9377
  return cached;
9365
9378
  }
9366
9379
  const dec = safeDecrypt(sessionId);
9367
- if (isValidPublicSessionPayload(dec)) {
9380
+ if (isValidPublicSessionPayload(dec, expectedAuthSig)) {
9368
9381
  cache.set(sessionId, dec);
9369
9382
  return dec;
9370
9383
  }
@@ -18182,7 +18195,7 @@ function isUIResourceUri(uri) {
18182
18195
  return uri.startsWith("ui://");
18183
18196
  }
18184
18197
  function parseWidgetUri(uri) {
18185
- const match = uri.match(/^ui:\/\/widget\/([^.]+)\.(\w+)$/);
18198
+ const match = uri.match(WIDGET_URI_PATTERN);
18186
18199
  if (!match) return null;
18187
18200
  return { toolName: match[1], extension: match[2] };
18188
18201
  }
@@ -18191,7 +18204,7 @@ function getUIResourceMimeType(_platformOrUri) {
18191
18204
  if (_platformOrUri?.endsWith(".css")) return "text/css";
18192
18205
  return MCP_APPS_MIME_TYPE;
18193
18206
  }
18194
- var ToolUIRegistry;
18207
+ var ToolUIRegistry, WIDGET_URI_PATTERN;
18195
18208
  var init_ui_shared = __esm({
18196
18209
  "libs/sdk/src/tool/ui/ui-shared.ts"() {
18197
18210
  "use strict";
@@ -18316,6 +18329,7 @@ var init_ui_shared = __esm({
18316
18329
  return { meta: result.meta };
18317
18330
  }
18318
18331
  };
18332
+ WIDGET_URI_PATTERN = /^ui:\/\/widget\/([A-Za-z0-9_:@-]+)\.(html|js|css)$/;
18319
18333
  }
18320
18334
  });
18321
18335
 
@@ -23202,8 +23216,180 @@ var init_adapter_registry = __esm({
23202
23216
  }
23203
23217
  });
23204
23218
 
23219
+ // libs/sdk/src/common/utils/path.utils.ts
23220
+ import { joinPath, trimSlashes } from "@frontmcp/utils";
23221
+ import { trimSlashes as trimSlashes2, joinPath as joinPath2 } from "@frontmcp/utils";
23222
+ function normalizeEntryPrefix(entryPath) {
23223
+ const t = trimSlashes(entryPath ?? "");
23224
+ return t ? `/${t}` : "";
23225
+ }
23226
+ function resolveEntryPath(entryPath) {
23227
+ if (entryPath !== void 0) return entryPath;
23228
+ return process.env["FRONTMCP_HTTP_ENTRY_PATH"] ?? "";
23229
+ }
23230
+ function normalizeScopeBase(scopeBase) {
23231
+ const t = trimSlashes(scopeBase ?? "");
23232
+ return t ? `/${t}` : "";
23233
+ }
23234
+ function readPublicUrlPin() {
23235
+ try {
23236
+ if (typeof process !== "undefined" && process.env) {
23237
+ const v = process.env["FRONTMCP_PUBLIC_URL"];
23238
+ if (typeof v === "string" && v.trim()) {
23239
+ return v.trim().replace(/\/+$/, "");
23240
+ }
23241
+ }
23242
+ } catch {
23243
+ }
23244
+ return void 0;
23245
+ }
23246
+ function isPublicUrlPinned() {
23247
+ return readPublicUrlPin() !== void 0;
23248
+ }
23249
+ function isProxyTrusted() {
23250
+ try {
23251
+ if (typeof process !== "undefined" && process.env) {
23252
+ const v = process.env["FRONTMCP_TRUST_PROXY"];
23253
+ return typeof v === "string" && /^(1|true|yes|on)$/i.test(v.trim());
23254
+ }
23255
+ } catch {
23256
+ }
23257
+ return false;
23258
+ }
23259
+ function getRequestBaseUrl(req, entryPath) {
23260
+ const pin = readPublicUrlPin();
23261
+ if (pin) {
23262
+ return `${pin}${entryPath ?? ""}`;
23263
+ }
23264
+ const trustProxy = isProxyTrusted();
23265
+ const fwdProto = trustProxy ? req.headers["x-forwarded-proto"] : void 0;
23266
+ const fwdHost = trustProxy ? req.headers["x-forwarded-host"] : void 0;
23267
+ const proto = fwdProto || req.protocol || "http";
23268
+ const host = fwdHost || req.headers["host"];
23269
+ return `${proto}://${host}${entryPath ?? ""}`;
23270
+ }
23271
+ function computeIssuer(req, entryPath, scopeBase) {
23272
+ const entryPrefix = normalizeEntryPrefix(entryPath);
23273
+ const scope = normalizeScopeBase(scopeBase);
23274
+ return `${getRequestBaseUrl(req)}${entryPrefix}${scope}`;
23275
+ }
23276
+ function computeResource(req, entryPath, scopeBase) {
23277
+ const entryPrefix = normalizeEntryPrefix(entryPath);
23278
+ const scope = normalizeScopeBase(scopeBase);
23279
+ return `${getRequestBaseUrl(req)}${entryPrefix}${scope}`;
23280
+ }
23281
+ function normalizeResourceUri(uri) {
23282
+ try {
23283
+ const url = new URL(uri);
23284
+ let normalized = `${url.protocol}//${url.hostname}`;
23285
+ const isDefaultPort = url.protocol === "https:" && url.port === "443" || url.protocol === "http:" && url.port === "80";
23286
+ if (url.port && !isDefaultPort) {
23287
+ normalized += `:${url.port}`;
23288
+ }
23289
+ let path3 = url.pathname;
23290
+ if (path3.length > 1 && path3.endsWith("/")) {
23291
+ path3 = path3.slice(0, -1);
23292
+ }
23293
+ normalized += path3;
23294
+ return normalized;
23295
+ } catch {
23296
+ return uri;
23297
+ }
23298
+ }
23299
+ function resourceUriMatches(provided, canonical) {
23300
+ return normalizeResourceUri(provided) === normalizeResourceUri(canonical);
23301
+ }
23302
+ function urlToSafeId(url) {
23303
+ const u = new URL(url);
23304
+ const raw = (u.host + (u.pathname && u.pathname !== "/" ? u.pathname : "")).replace(/\/+$/, "");
23305
+ return trimSlashes(raw).replace(/[^a-zA-Z0-9_-]/g, "-");
23306
+ }
23307
+ function makeWellKnownPaths(name42, entryPrefix, scopeBase = "") {
23308
+ const prefix = normalizeEntryPrefix(entryPrefix);
23309
+ const scope = normalizeScopeBase(scopeBase);
23310
+ const reversed = joinPath(".well-known", name42) + `${prefix}${scope}`;
23311
+ const inPrefixRoot = `${prefix}${joinPath(".well-known", name42)}${scope}`;
23312
+ const inPrefixScope = `${prefix}${scope}${joinPath(".well-known", name42)}`;
23313
+ return /* @__PURE__ */ new Set([reversed, inPrefixRoot, inPrefixScope]);
23314
+ }
23315
+ var init_path_utils = __esm({
23316
+ "libs/sdk/src/common/utils/path.utils.ts"() {
23317
+ "use strict";
23318
+ }
23319
+ });
23320
+
23205
23321
  // libs/sdk/src/context/metadata.utils.ts
23206
- function extractMetadata(headers) {
23322
+ function isIpv4(value) {
23323
+ const match = IPV4_PATTERN.exec(value);
23324
+ if (!match) return false;
23325
+ return match.slice(1).every((octet) => {
23326
+ if (octet.length > 1 && octet.startsWith("0")) return false;
23327
+ return Number(octet) <= 255;
23328
+ });
23329
+ }
23330
+ function isIpv6(value) {
23331
+ const text = value.includes("%") ? value.slice(0, value.indexOf("%")) : value;
23332
+ if (text.length === 0) return false;
23333
+ let head = text;
23334
+ let embedded = 0;
23335
+ const lastColon = head.lastIndexOf(":");
23336
+ const tail = lastColon === -1 ? "" : head.slice(lastColon + 1);
23337
+ if (tail.includes(".")) {
23338
+ if (!isIpv4(tail)) return false;
23339
+ head = head.slice(0, lastColon + 1);
23340
+ embedded = 2;
23341
+ }
23342
+ const halves = head.split("::");
23343
+ if (halves.length > 2) return false;
23344
+ const countGroups = (part) => {
23345
+ if (part === "") return 0;
23346
+ const groups = part.split(":").filter((group, index, all) => !(group === "" && index === all.length - 1));
23347
+ for (const group of groups) {
23348
+ if (!/^[0-9A-Fa-f]{1,4}$/.test(group)) return null;
23349
+ }
23350
+ return groups.length;
23351
+ };
23352
+ const headCount = countGroups(halves[0]);
23353
+ if (headCount === null) return false;
23354
+ if (halves.length === 1) {
23355
+ return headCount + embedded === 8;
23356
+ }
23357
+ const tailCount = countGroups(halves[1]);
23358
+ if (tailCount === null) return false;
23359
+ return headCount + tailCount + embedded <= 7;
23360
+ }
23361
+ function isIpAddress(value) {
23362
+ const candidate = value.startsWith("[") && value.endsWith("]") ? value.slice(1, -1) : value;
23363
+ return candidate.includes(":") ? isIpv6(candidate) : isIpv4(candidate);
23364
+ }
23365
+ function normalizeIp(value) {
23366
+ if (typeof value !== "string") return void 0;
23367
+ const trimmed = value.trim();
23368
+ if (!trimmed) return void 0;
23369
+ return isIpAddress(trimmed) ? trimmed : void 0;
23370
+ }
23371
+ function readTrustedProxyDepth() {
23372
+ try {
23373
+ if (typeof process !== "undefined" && process.env) {
23374
+ const parsed = Number(process.env["FRONTMCP_TRUSTED_PROXY_DEPTH"]);
23375
+ if (Number.isInteger(parsed) && parsed > 0) return parsed;
23376
+ }
23377
+ } catch {
23378
+ }
23379
+ return 1;
23380
+ }
23381
+ function normalizeDepth(depth) {
23382
+ if (depth === void 0) return readTrustedProxyDepth();
23383
+ return Number.isInteger(depth) && depth > 0 ? depth : readTrustedProxyDepth();
23384
+ }
23385
+ function headerValues(header) {
23386
+ if (typeof header === "string") return header.split(",");
23387
+ if (Array.isArray(header)) {
23388
+ return header.filter((entry) => typeof entry === "string").flatMap((entry) => entry.split(","));
23389
+ }
23390
+ return [];
23391
+ }
23392
+ function extractMetadata(headers, options) {
23207
23393
  const customHeaders = {};
23208
23394
  for (const [key, value] of Object.entries(headers)) {
23209
23395
  if (key.toLowerCase().startsWith("x-frontmcp-") && typeof value === "string") {
@@ -23214,30 +23400,34 @@ function extractMetadata(headers) {
23214
23400
  userAgent: typeof headers["user-agent"] === "string" ? headers["user-agent"] : void 0,
23215
23401
  contentType: typeof headers["content-type"] === "string" ? headers["content-type"] : void 0,
23216
23402
  accept: typeof headers["accept"] === "string" ? headers["accept"] : void 0,
23217
- clientIp: extractClientIp(headers),
23403
+ clientIp: extractClientIp(headers, options),
23218
23404
  customHeaders
23219
23405
  };
23220
23406
  }
23221
- function extractClientIp(headers) {
23222
- const xff = headers["x-forwarded-for"];
23223
- if (typeof xff === "string") {
23224
- return xff.split(",")[0]?.trim();
23225
- }
23226
- if (Array.isArray(xff) && typeof xff[0] === "string") {
23227
- return xff[0].split(",")[0]?.trim();
23407
+ function extractClientIp(headers, options) {
23408
+ const peerIp = normalizeIp(options?.peerAddress);
23409
+ const trustProxy = options?.trustProxy ?? isProxyTrusted();
23410
+ if (!trustProxy) {
23411
+ return peerIp;
23228
23412
  }
23229
- const realIp = headers["x-real-ip"];
23230
- if (typeof realIp === "string") {
23231
- return realIp;
23413
+ const depth = normalizeDepth(options?.trustedProxyDepth);
23414
+ const forwarded = headerValues(headers["x-forwarded-for"]).map((entry) => entry.trim()).filter((entry) => entry.length > 0);
23415
+ if (forwarded.length > 0) {
23416
+ if (forwarded.length < depth) return peerIp;
23417
+ return normalizeIp(forwarded[forwarded.length - depth]) ?? peerIp;
23232
23418
  }
23233
- if (Array.isArray(realIp) && typeof realIp[0] === "string") {
23234
- return realIp[0];
23419
+ if (depth === 1) {
23420
+ const realIp = normalizeIp(headerValues(headers["x-real-ip"])[0]);
23421
+ if (realIp) return realIp;
23235
23422
  }
23236
- return void 0;
23423
+ return peerIp;
23237
23424
  }
23425
+ var IPV4_PATTERN;
23238
23426
  var init_metadata_utils = __esm({
23239
23427
  "libs/sdk/src/context/metadata.utils.ts"() {
23240
23428
  "use strict";
23429
+ init_path_utils();
23430
+ IPV4_PATTERN = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/;
23241
23431
  }
23242
23432
  });
23243
23433
 
@@ -23282,10 +23472,11 @@ var init_frontmcp_context_storage = __esm({
23282
23472
  * @returns Result of the callback
23283
23473
  */
23284
23474
  runFromHeaders(headers, args, fn) {
23475
+ const { peerAddress, ...contextArgs } = args;
23285
23476
  const traceContext = parseTraceContext(headers);
23286
- const metadata = extractMetadata(headers);
23477
+ const metadata = extractMetadata(headers, { peerAddress });
23287
23478
  const context = new FrontMcpContext({
23288
- ...args,
23479
+ ...contextArgs,
23289
23480
  traceContext,
23290
23481
  metadata
23291
23482
  });
@@ -24465,11 +24656,13 @@ var init_flow_instance = __esm({
24465
24656
  const headerSessionId = typeof headers["mcp-session-id"] === "string" ? headers["mcp-session-id"].trim() : "";
24466
24657
  const sessionId = headerSessionId.length > 0 ? headerSessionId : `anon:${randomUUID13()}`;
24467
24658
  const scope = this.globalProviders.getActiveScope();
24659
+ const peerAddress = request.socket?.remoteAddress;
24468
24660
  return storage.runFromHeaders(
24469
24661
  headers,
24470
24662
  {
24471
24663
  sessionId,
24472
- scopeId: scope.id
24664
+ scopeId: scope.id,
24665
+ peerAddress
24473
24666
  },
24474
24667
  async () => {
24475
24668
  return this.run(input, deps);
@@ -34413,13 +34606,13 @@ var init_esm_module_loader = __esm({
34413
34606
  URL.revokeObjectURL(url);
34414
34607
  }
34415
34608
  }
34416
- const { ensureDir, fileExists: fileExists9, sha256Hex: sha256Hex11, writeFile } = await import("@frontmcp/utils");
34609
+ const { ensureDir, fileExists: fileExists9, sha256Hex: sha256Hex12, writeFile } = await import("@frontmcp/utils");
34417
34610
  const nodePath = await import("node:path");
34418
34611
  const nodeOs = await import("node:os");
34419
34612
  const { pathToFileURL } = await import("node:url");
34420
34613
  const dir = nodePath.join(nodeOs.tmpdir(), "frontmcp-esm-modules");
34421
34614
  await ensureDir(dir);
34422
- const tempPath = nodePath.join(dir, `${sha256Hex11(bundleContent)}.mjs`);
34615
+ const tempPath = nodePath.join(dir, `${sha256Hex12(bundleContent)}.mjs`);
34423
34616
  if (!await fileExists9(tempPath)) {
34424
34617
  await writeFile(tempPath, bundleContent);
34425
34618
  }
@@ -36340,6 +36533,7 @@ var init_oauth_authorize_flow = __esm({
36340
36533
  const localAuth = auth;
36341
36534
  const store = localAuth.authorizationStore;
36342
36535
  let effectiveIncremental = isIncrementalAuth;
36536
+ let effectivePriorAppIds = priorAuthorizedAppIds;
36343
36537
  if (incrementalTicket) {
36344
36538
  const claimed = await localAuth.claimIncrementalTicket(
36345
36539
  incrementalTicket.jti,
@@ -36348,7 +36542,13 @@ var init_oauth_authorize_flow = __esm({
36348
36542
  if (!claimed) {
36349
36543
  this.logger.warn("Incremental auth ticket already used \u2014 falling back to an ordinary login");
36350
36544
  effectiveIncremental = false;
36351
- this.state.set({ isIncrementalAuth: false, targetAppId: void 0, targetToolId: void 0 });
36545
+ effectivePriorAppIds = void 0;
36546
+ this.state.set({
36547
+ isIncrementalAuth: false,
36548
+ targetAppId: void 0,
36549
+ targetToolId: void 0,
36550
+ priorAuthorizedAppIds: void 0
36551
+ });
36352
36552
  }
36353
36553
  }
36354
36554
  let federatedLogin;
@@ -36407,7 +36607,7 @@ var init_oauth_authorize_flow = __esm({
36407
36607
  targetAppId: effectiveIncremental ? targetAppId : void 0,
36408
36608
  targetToolId: effectiveIncremental ? targetToolId : void 0,
36409
36609
  existingSessionId,
36410
- priorAuthorizedAppIds,
36610
+ priorAuthorizedAppIds: effectivePriorAppIds,
36411
36611
  // Federated Login State
36412
36612
  federatedLogin,
36413
36613
  // Consent State
@@ -39585,7 +39785,23 @@ import {
39585
39785
  validateAudience
39586
39786
  } from "@frontmcp/auth";
39587
39787
  import { z as z71 } from "@frontmcp/lazy-zod";
39588
- import { getMachineId as getMachineId2, randomUUID as randomUUID20 } from "@frontmcp/utils";
39788
+ import { getMachineId as getMachineId2, randomUUID as randomUUID20, sha256, sha256Hex as sha256Hex9, timingSafeEqual as timingSafeEqual2 } from "@frontmcp/utils";
39789
+ function constantTimeEquals(a, b) {
39790
+ const encoder5 = new TextEncoder();
39791
+ return timingSafeEqual2(sha256(encoder5.encode(a)), sha256(encoder5.encode(b)));
39792
+ }
39793
+ function quoteHeaderParam(value) {
39794
+ return value.replace(/[^\x20-\x21\x23-\x7e]/g, "");
39795
+ }
39796
+ function buildStaticChallenge(options, description) {
39797
+ const scheme = options.scheme.trim();
39798
+ if (!scheme) return "";
39799
+ const parts = [`${quoteHeaderParam(scheme)} realm="${quoteHeaderParam(options.realm)}"`];
39800
+ if (description) {
39801
+ parts.push('error="invalid_token"', `error_description="${quoteHeaderParam(description)}"`);
39802
+ }
39803
+ return parts.join(", ");
39804
+ }
39589
39805
  var inputSchema23, stateSchema22, UnauthorizedSchema, AuthorizedSchema, ForbiddenSchema, sessionVerifyOutputSchema, plan22, name22, Stage22, warnedUnpinnedAudience, SessionVerifyFlow;
39590
39806
  var init_session_verify_flow = __esm({
39591
39807
  "libs/sdk/src/auth/flows/session.verify.flow.ts"() {
@@ -39626,7 +39842,14 @@ var init_session_verify_flow = __esm({
39626
39842
  }).describe("403 Forbidden \u2014 token valid but insufficient scope");
39627
39843
  sessionVerifyOutputSchema = z71.union([UnauthorizedSchema, AuthorizedSchema, ForbiddenSchema]);
39628
39844
  plan22 = {
39629
- pre: ["parseInput", "handlePublicMode", "handleAnonymousFallback", "requireAuthorizationHeader", "verifyIfJwt"],
39845
+ pre: [
39846
+ "parseInput",
39847
+ "handleStaticToken",
39848
+ "handlePublicMode",
39849
+ "handleAnonymousFallback",
39850
+ "requireAuthorizationHeader",
39851
+ "verifyIfJwt"
39852
+ ],
39630
39853
  execute: ["deriveUser", "parseSessionHeader", "buildAuthorizedOutput"]
39631
39854
  };
39632
39855
  name22 = "session:verify";
@@ -39644,14 +39867,20 @@ var init_session_verify_flow = __esm({
39644
39867
  * Encapsulates the shared logic for session creation, payload encryption, and user derivation.
39645
39868
  */
39646
39869
  createAnonymousSession(options) {
39647
- const { authMode, issuer, scopes = ["anonymous"], sessionIdHeader } = options;
39870
+ const { authMode, issuer, scopes = ["anonymous"], subject, sessionIdHeader } = options;
39648
39871
  this.logger.verbose("createAnonymousSession", { authMode, hasExistingSession: !!sessionIdHeader });
39649
39872
  const machineId = getMachineId2();
39873
+ const authSignature = subject ? `${authMode}:${subject}` : authMode;
39650
39874
  if (sessionIdHeader) {
39651
- const existingPayload = decryptPublicSession(sessionIdHeader);
39875
+ const existingPayload = decryptPublicSession(sessionIdHeader, authSignature);
39652
39876
  if (existingPayload && existingPayload.nodeId === machineId) {
39653
39877
  const anonId = existingPayload.uuid ?? `${existingPayload.iat * 1e3}`;
39654
- const user2 = { sub: `anon:${anonId}`, iss: issuer, name: "Anonymous", scope: scopes.join(" ") };
39878
+ const user2 = {
39879
+ sub: subject ?? `anon:${anonId}`,
39880
+ iss: issuer,
39881
+ name: subject ? "Static token" : "Anonymous",
39882
+ scope: scopes.join(" ")
39883
+ };
39655
39884
  this.respond({
39656
39885
  kind: "authorized",
39657
39886
  authorization: {
@@ -39666,9 +39895,9 @@ var init_session_verify_flow = __esm({
39666
39895
  }
39667
39896
  const now = Date.now();
39668
39897
  const user = {
39669
- sub: `anon:${randomUUID20()}`,
39898
+ sub: subject ?? `anon:${randomUUID20()}`,
39670
39899
  iss: issuer,
39671
- name: "Anonymous",
39900
+ name: subject ? "Static token" : "Anonymous",
39672
39901
  scope: scopes.join(" ")
39673
39902
  };
39674
39903
  const uuid = randomUUID20();
@@ -39677,7 +39906,7 @@ var init_session_verify_flow = __esm({
39677
39906
  const payload = {
39678
39907
  uuid,
39679
39908
  nodeId: machineId,
39680
- authSig: authMode,
39909
+ authSig: authSignature,
39681
39910
  iat: Math.floor(now / 1e3),
39682
39911
  isPublic: authMode === "public",
39683
39912
  authMode,
@@ -39727,14 +39956,68 @@ var init_session_verify_flow = __esm({
39727
39956
  prmUrl
39728
39957
  });
39729
39958
  }
39959
+ async handleStaticToken() {
39960
+ const authOptions = this.scope.auth?.options;
39961
+ if (!authOptions || !isStaticMode(authOptions)) return;
39962
+ const options = authOptions;
39963
+ const presented = this.readStaticCredential(options);
39964
+ if (!presented) {
39965
+ this.logger.warn("handleStaticToken: no credential presented, returning 401");
39966
+ this.respond({
39967
+ kind: "unauthorized",
39968
+ prmMetadataHeader: buildStaticChallenge(options)
39969
+ });
39970
+ return;
39971
+ }
39972
+ const matched = options.tokens.reduce(
39973
+ (found, candidate) => constantTimeEquals(candidate, presented) ? candidate : found,
39974
+ void 0
39975
+ );
39976
+ if (!matched) {
39977
+ this.logger.warn("handleStaticToken: credential did not match, returning 401");
39978
+ this.respond({
39979
+ kind: "unauthorized",
39980
+ prmMetadataHeader: buildStaticChallenge(options, "The access token is invalid")
39981
+ });
39982
+ return;
39983
+ }
39984
+ this.logger.verbose("handleStaticToken: credential accepted");
39985
+ this.createAnonymousSession({
39986
+ authMode: "static",
39987
+ issuer: "static",
39988
+ scopes: options.scopes,
39989
+ subject: `static:${sha256Hex9(matched).slice(0, 12)}`,
39990
+ sessionIdHeader: this.state.sessionIdHeader
39991
+ });
39992
+ }
39993
+ /**
39994
+ * Pull the credential out of the configured header, stripping the scheme
39995
+ * prefix when one is configured (case-insensitively, per RFC 7235).
39996
+ */
39997
+ readStaticCredential(options) {
39998
+ const headers = this.rawInput.request.headers ?? {};
39999
+ const raw = headers[options.header.toLowerCase()];
40000
+ const value = (Array.isArray(raw) ? raw[0] : raw)?.trim();
40001
+ if (!value) return void 0;
40002
+ const { scheme } = options;
40003
+ if (!scheme) return value;
40004
+ if (value.slice(0, scheme.length).toLowerCase() !== scheme.toLowerCase()) return void 0;
40005
+ if (value[scheme.length] !== " ") return void 0;
40006
+ return value.slice(scheme.length + 1).trim() || void 0;
40007
+ }
39730
40008
  async handlePublicMode() {
39731
40009
  const authOptions = this.scope.auth?.options;
39732
40010
  if (!authOptions || !isPublicMode(authOptions)) {
39733
40011
  return;
39734
40012
  }
39735
- if (this.state.token) {
40013
+ if (this.state.token && isJwt(this.state.token)) {
39736
40014
  return;
39737
40015
  }
40016
+ if (this.state.token) {
40017
+ this.logger.verbose(
40018
+ "handlePublicMode: ignoring non-JWT bearer token (public mode has nothing to verify it against)"
40019
+ );
40020
+ }
39738
40021
  this.logger.info("handlePublicMode: allowing anonymous access (public mode)");
39739
40022
  this.createAnonymousSession({
39740
40023
  authMode: "public",
@@ -39908,6 +40191,14 @@ var init_session_verify_flow = __esm({
39908
40191
  __decorateClass([
39909
40192
  Stage22("parseInput")
39910
40193
  ], SessionVerifyFlow.prototype, "parseInput", 1);
40194
+ __decorateClass([
40195
+ Stage22("handleStaticToken", {
40196
+ filter: ({ scope }) => {
40197
+ const authOptions = scope.auth?.options;
40198
+ return !!authOptions && isStaticMode(authOptions);
40199
+ }
40200
+ })
40201
+ ], SessionVerifyFlow.prototype, "handleStaticToken", 1);
39911
40202
  __decorateClass([
39912
40203
  Stage22("handlePublicMode")
39913
40204
  ], SessionVerifyFlow.prototype, "handlePublicMode", 1);
@@ -40402,7 +40693,7 @@ import {
40402
40693
  MemoryStorageAdapter,
40403
40694
  randomBytes as randomBytes6,
40404
40695
  randomUUID as randomUUID21,
40405
- sha256Hex as sha256Hex9,
40696
+ sha256Hex as sha256Hex10,
40406
40697
  StorageNotSupportedError
40407
40698
  } from "@frontmcp/utils";
40408
40699
  function getDefaultNoAuthSecret() {
@@ -40428,13 +40719,14 @@ function validateAuthorizationIssuer(received, expected) {
40428
40719
  function isUnsupportedOperation(error) {
40429
40720
  return error instanceof StorageNotSupportedError;
40430
40721
  }
40431
- var defaultNoAuthSecret, RESERVED_JWT_CLAIMS, LocalPrimaryAuth;
40722
+ var MIN_HS256_SECRET_BYTES, defaultNoAuthSecret, RESERVED_JWT_CLAIMS, LocalPrimaryAuth;
40432
40723
  var init_instance_local_primary_auth = __esm({
40433
40724
  "libs/sdk/src/auth/instances/instance.local-primary-auth.ts"() {
40434
40725
  "use strict";
40435
40726
  init_common();
40436
40727
  init_auth2();
40437
40728
  init_context_extension();
40729
+ init_errors();
40438
40730
  init_cimd();
40439
40731
  init_credentials();
40440
40732
  init_credentials_context_extension();
@@ -40452,6 +40744,7 @@ var init_instance_local_primary_auth = __esm({
40452
40744
  init_well_known_prm_flow();
40453
40745
  init_secure_store();
40454
40746
  init_secure_store_context_extension();
40747
+ MIN_HS256_SECRET_BYTES = 32;
40455
40748
  RESERVED_JWT_CLAIMS = /* @__PURE__ */ new Set([
40456
40749
  "sub",
40457
40750
  "iss",
@@ -40479,10 +40772,22 @@ var init_instance_local_primary_auth = __esm({
40479
40772
  this.host = getEnv4("FRONTMCP_PUBLIC_HOST")?.trim() || "localhost";
40480
40773
  this.issuer = this.deriveIssuer(options);
40481
40774
  const jwtSecret = getEnv4("JWT_SECRET");
40482
- if (jwtSecret) {
40483
- this.secret = new TextEncoder().encode(jwtSecret);
40775
+ const hasJwtSecret = !!jwtSecret?.trim();
40776
+ if (hasJwtSecret && jwtSecret) {
40777
+ const encoded = new TextEncoder().encode(jwtSecret);
40778
+ if (encoded.length < MIN_HS256_SECRET_BYTES) {
40779
+ if (isProduction4()) throw new JwtSecretWeakError(encoded.length);
40780
+ this.logger.warn(
40781
+ `JWT_SECRET is ${encoded.length} bytes; HS256 wants at least ${MIN_HS256_SECRET_BYTES} (RFC 7518). This is refused in production \u2014 generate one with \`openssl rand -hex 32\`.`
40782
+ );
40783
+ }
40784
+ this.secret = encoded;
40785
+ } else if (isProduction4() && isOrchestratedMode(options)) {
40786
+ throw new JwtSecretRequiredError(options.mode);
40484
40787
  } else {
40485
- this.logger.warn("JWT_SECRET is not set, using default secret");
40788
+ this.logger.warn(
40789
+ "JWT_SECRET is not set; signing with a random per-process secret. Tokens will not survive a restart and will not verify across instances. Set JWT_SECRET for any deployment that mints tokens."
40790
+ );
40486
40791
  this.secret = getDefaultNoAuthSecret();
40487
40792
  }
40488
40793
  this.tokenStorage = this.readTokenStorage(options);
@@ -41042,7 +41347,7 @@ var init_instance_local_primary_auth = __esm({
41042
41347
  const pendingAuthId = `pending:${codeRecord.pendingAuthId}`;
41043
41348
  const parts = accessToken.split(".");
41044
41349
  const signature = parts[2] || accessToken;
41045
- const newAuthId = sha256Hex9(signature).substring(0, 16);
41350
+ const newAuthId = sha256Hex10(signature).substring(0, 16);
41046
41351
  await this.orchestratedTokenStore.migrateTokens(pendingAuthId, newAuthId);
41047
41352
  this.logger.info(`Migrated tokens from ${pendingAuthId} to ${newAuthId}`);
41048
41353
  } catch (err) {
@@ -41754,6 +42059,9 @@ var init_auth_registry = __esm({
41754
42059
  if (isPublicMode(options)) {
41755
42060
  return new LocalPrimaryAuth(scope, providers, options);
41756
42061
  }
42062
+ if (isStaticMode(options)) {
42063
+ return new LocalPrimaryAuth(scope, providers, options);
42064
+ }
41757
42065
  if (isTransparentMode(options)) {
41758
42066
  return new RemotePrimaryAuth(scope, providers, options);
41759
42067
  }
@@ -45862,7 +46170,9 @@ var init_workflow_step_executor = __esm({
45862
46170
  "libs/sdk/src/workflow/engine/workflow-step.executor.ts"() {
45863
46171
  "use strict";
45864
46172
  init_errors();
46173
+ init_job_errors();
45865
46174
  init_workflow_errors();
46175
+ init_job_permission_guard();
45866
46176
  WorkflowStepExecutor = class {
45867
46177
  jobRegistry;
45868
46178
  logger;
@@ -45877,6 +46187,16 @@ var init_workflow_step_executor = __esm({
45877
46187
  if (!job) {
45878
46188
  throw new InvalidEntityError("job", step.jobName, `a registered job (referenced by step "${step.id}")`);
45879
46189
  }
46190
+ const allowed = await JobPermissionGuard.check(
46191
+ job.metadata.permissions,
46192
+ "execute",
46193
+ this.extra.authInfo,
46194
+ this.extra.authoritiesContextBuilder
46195
+ );
46196
+ if (!allowed) {
46197
+ this.logger.warn(`Step "${step.id}" denied: caller does not satisfy the execute permissions of "${job.name}"`);
46198
+ throw new JobNotAuthorizedError(job.name);
46199
+ }
45880
46200
  const retryConfig = step.retry ?? job.metadata.retry ?? {};
45881
46201
  const maxAttempts = retryConfig.maxAttempts ?? 3;
45882
46202
  const backoffMs = retryConfig.backoffMs ?? 1e3;
@@ -46290,7 +46610,8 @@ var init_job_execution_manager = __esm({
46290
46610
  try {
46291
46611
  const engine = new WorkflowEngine(workflow.metadata, jobRegistry, this.logger, {
46292
46612
  authInfo: opts.authInfo ?? {},
46293
- contextProviders: opts.contextProviders
46613
+ contextProviders: opts.contextProviders,
46614
+ authoritiesContextBuilder: opts.authoritiesContextBuilder
46294
46615
  });
46295
46616
  const result = await engine.execute(opts.workflowInput);
46296
46617
  await this.stateStore.updateRun(runId, {
@@ -49204,7 +49525,7 @@ var init_load_skill_flow = __esm({
49204
49525
  });
49205
49526
 
49206
49527
  // libs/sdk/src/skill/auth/skill-http-auth.ts
49207
- import { timingSafeEqual as timingSafeEqual2 } from "@frontmcp/utils";
49528
+ import { timingSafeEqual as timingSafeEqual3 } from "@frontmcp/utils";
49208
49529
  function createSkillHttpAuthValidator(skillsConfig, logger) {
49209
49530
  if (!skillsConfig?.auth || skillsConfig.auth === "public" || skillsConfig.auth === "inherit") {
49210
49531
  return null;
@@ -49291,7 +49612,7 @@ var init_skill_http_auth = __esm({
49291
49612
  const keyBytes = encoder5.encode(key);
49292
49613
  if (keyBytes.length === candidateBytes.length) {
49293
49614
  try {
49294
- if (timingSafeEqual2(keyBytes, candidateBytes)) {
49615
+ if (timingSafeEqual3(keyBytes, candidateBytes)) {
49295
49616
  found = true;
49296
49617
  }
49297
49618
  } catch {
@@ -52257,104 +52578,6 @@ var init_decide_request_intent_utils = __esm({
52257
52578
  }
52258
52579
  });
52259
52580
 
52260
- // libs/sdk/src/common/utils/path.utils.ts
52261
- import { joinPath, trimSlashes } from "@frontmcp/utils";
52262
- import { trimSlashes as trimSlashes2, joinPath as joinPath2 } from "@frontmcp/utils";
52263
- function normalizeEntryPrefix(entryPath) {
52264
- const t = trimSlashes(entryPath ?? "");
52265
- return t ? `/${t}` : "";
52266
- }
52267
- function normalizeScopeBase(scopeBase) {
52268
- const t = trimSlashes(scopeBase ?? "");
52269
- return t ? `/${t}` : "";
52270
- }
52271
- function readPublicUrlPin() {
52272
- try {
52273
- if (typeof process !== "undefined" && process.env) {
52274
- const v = process.env["FRONTMCP_PUBLIC_URL"];
52275
- if (typeof v === "string" && v.trim()) {
52276
- return v.trim().replace(/\/+$/, "");
52277
- }
52278
- }
52279
- } catch {
52280
- }
52281
- return void 0;
52282
- }
52283
- function isPublicUrlPinned() {
52284
- return readPublicUrlPin() !== void 0;
52285
- }
52286
- function isProxyTrusted() {
52287
- try {
52288
- if (typeof process !== "undefined" && process.env) {
52289
- const v = process.env["FRONTMCP_TRUST_PROXY"];
52290
- return typeof v === "string" && /^(1|true|yes|on)$/i.test(v.trim());
52291
- }
52292
- } catch {
52293
- }
52294
- return false;
52295
- }
52296
- function getRequestBaseUrl(req, entryPath) {
52297
- const pin = readPublicUrlPin();
52298
- if (pin) {
52299
- return `${pin}${entryPath ?? ""}`;
52300
- }
52301
- const trustProxy = isProxyTrusted();
52302
- const fwdProto = trustProxy ? req.headers["x-forwarded-proto"] : void 0;
52303
- const fwdHost = trustProxy ? req.headers["x-forwarded-host"] : void 0;
52304
- const proto = fwdProto || req.protocol || "http";
52305
- const host = fwdHost || req.headers["host"];
52306
- return `${proto}://${host}${entryPath ?? ""}`;
52307
- }
52308
- function computeIssuer(req, entryPath, scopeBase) {
52309
- const entryPrefix = normalizeEntryPrefix(entryPath);
52310
- const scope = normalizeScopeBase(scopeBase);
52311
- return `${getRequestBaseUrl(req)}${entryPrefix}${scope}`;
52312
- }
52313
- function computeResource(req, entryPath, scopeBase) {
52314
- const entryPrefix = normalizeEntryPrefix(entryPath);
52315
- const scope = normalizeScopeBase(scopeBase);
52316
- return `${getRequestBaseUrl(req)}${entryPrefix}${scope}`;
52317
- }
52318
- function normalizeResourceUri(uri) {
52319
- try {
52320
- const url = new URL(uri);
52321
- let normalized = `${url.protocol}//${url.hostname}`;
52322
- const isDefaultPort = url.protocol === "https:" && url.port === "443" || url.protocol === "http:" && url.port === "80";
52323
- if (url.port && !isDefaultPort) {
52324
- normalized += `:${url.port}`;
52325
- }
52326
- let path3 = url.pathname;
52327
- if (path3.length > 1 && path3.endsWith("/")) {
52328
- path3 = path3.slice(0, -1);
52329
- }
52330
- normalized += path3;
52331
- return normalized;
52332
- } catch {
52333
- return uri;
52334
- }
52335
- }
52336
- function resourceUriMatches(provided, canonical) {
52337
- return normalizeResourceUri(provided) === normalizeResourceUri(canonical);
52338
- }
52339
- function urlToSafeId(url) {
52340
- const u = new URL(url);
52341
- const raw = (u.host + (u.pathname && u.pathname !== "/" ? u.pathname : "")).replace(/\/+$/, "");
52342
- return trimSlashes(raw).replace(/[^a-zA-Z0-9_-]/g, "-");
52343
- }
52344
- function makeWellKnownPaths(name42, entryPrefix, scopeBase = "") {
52345
- const prefix = normalizeEntryPrefix(entryPrefix);
52346
- const scope = normalizeScopeBase(scopeBase);
52347
- const reversed = joinPath(".well-known", name42) + `${prefix}${scope}`;
52348
- const inPrefixRoot = `${prefix}${joinPath(".well-known", name42)}${scope}`;
52349
- const inPrefixScope = `${prefix}${scope}${joinPath(".well-known", name42)}`;
52350
- return /* @__PURE__ */ new Set([reversed, inPrefixRoot, inPrefixScope]);
52351
- }
52352
- var init_path_utils = __esm({
52353
- "libs/sdk/src/common/utils/path.utils.ts"() {
52354
- "use strict";
52355
- }
52356
- });
52357
-
52358
52581
  // libs/sdk/src/common/utils/global-config.utils.ts
52359
52582
  function getGlobalStoreConfig(pluginName, config) {
52360
52583
  if (!config.redis) {
@@ -52689,7 +52912,7 @@ async function tryResolvedSubFile(instance, kind, tail) {
52689
52912
  return parseSkillMdFrontmatter(inline).body;
52690
52913
  }
52691
52914
  const absolutePath = entry.path;
52692
- const resolvedPath = resolveEntryPath(instance, kind, absolutePath, filename);
52915
+ const resolvedPath = resolveEntryPath2(instance, kind, absolutePath, filename);
52693
52916
  if (!resolvedPath) continue;
52694
52917
  try {
52695
52918
  const buf = await readFileBuffer(resolvedPath);
@@ -52704,7 +52927,7 @@ async function tryResolvedSubFile(instance, kind, tail) {
52704
52927
  }
52705
52928
  return void 0;
52706
52929
  }
52707
- function resolveEntryPath(instance, kind, absolutePath, filename) {
52930
+ function resolveEntryPath2(instance, kind, absolutePath, filename) {
52708
52931
  if (absolutePath && isAbsolute3(absolutePath)) return absolutePath;
52709
52932
  if (!filename) return void 0;
52710
52933
  const baseDir = instance.getBaseDir();
@@ -56845,7 +57068,7 @@ var init_transport_remote = __esm({
56845
57068
  });
56846
57069
 
56847
57070
  // libs/sdk/src/transport/transport.registry.ts
56848
- import { getMachineId as getMachineId5, sha256Hex as sha256Hex10 } from "@frontmcp/utils";
57071
+ import { getMachineId as getMachineId5, sha256Hex as sha256Hex11 } from "@frontmcp/utils";
56849
57072
  var TransportService;
56850
57073
  var init_transport_registry = __esm({
56851
57074
  "libs/sdk/src/transport/transport.registry.ts"() {
@@ -57381,7 +57604,7 @@ var init_transport_registry = __esm({
57381
57604
  }
57382
57605
  /* --------------------------------- internals -------------------------------- */
57383
57606
  sha256(value) {
57384
- return sha256Hex10(value);
57607
+ return sha256Hex11(value);
57385
57608
  }
57386
57609
  /**
57387
57610
  * Create a history key from components.
@@ -57674,13 +57897,28 @@ var init_http_request_flow = __esm({
57674
57897
  }
57675
57898
  async acquireQuota() {
57676
57899
  const manager = this.scope.rateLimitManager;
57677
- if (!manager?.config?.global) return;
57900
+ if (!manager) return;
57678
57901
  const context = this.tryGetContext();
57679
57902
  const partitionCtx = context ? {
57680
57903
  sessionId: context.sessionId,
57681
57904
  clientIp: context.metadata?.clientIp,
57682
57905
  userId: context.authInfo?.clientId
57683
57906
  } : void 0;
57907
+ const ipResult = manager.checkIpFilter(partitionCtx?.clientIp);
57908
+ if (ipResult && !ipResult.allowed) {
57909
+ this.logger.warn(`[${this.requestId}] request rejected by ipFilter`, { reason: ipResult.reason });
57910
+ this.respond(
57911
+ httpRespond.json(
57912
+ {
57913
+ jsonrpc: "2.0",
57914
+ error: { code: -32001, message: "Forbidden: client IP rejected by ipFilter" }
57915
+ },
57916
+ { status: 403 }
57917
+ )
57918
+ );
57919
+ return;
57920
+ }
57921
+ if (!manager.config?.global) return;
57684
57922
  const result = await manager.checkGlobalRateLimit(partitionCtx);
57685
57923
  if (!result.allowed) {
57686
57924
  const retryAfter = Math.ceil((result.retryAfterMs ?? 6e4) / 1e3);
@@ -58301,6 +58539,7 @@ var init_scope_instance = __esm({
58301
58539
  init_channel_scope_helper();
58302
58540
  init_common();
58303
58541
  init_sqlite();
58542
+ init_path_utils();
58304
58543
  init_complete_flow();
58305
58544
  init_context();
58306
58545
  init_elicitation2();
@@ -58401,7 +58640,7 @@ var init_scope_instance = __esm({
58401
58640
  this.logger = globalProviders.get(FrontMcpLogger).child("FrontMcp.MultiAppScope");
58402
58641
  this.globalProviders = globalProviders;
58403
58642
  this.server = this.globalProviders.get(FrontMcpServer);
58404
- this.entryPath = rec.metadata.http?.entryPath ?? "";
58643
+ this.entryPath = resolveEntryPath(rec.metadata.http?.entryPath);
58405
58644
  if (rec.kind === "SPLIT_BY_APP") {
58406
58645
  this.routeBase = `/${rec.metadata.id}`;
58407
58646
  } else {
@@ -58505,16 +58744,25 @@ var init_scope_instance = __esm({
58505
58744
  })() : void 0;
58506
58745
  const tasksConfig = this.metadata.tasks;
58507
58746
  const tasksExplicitlyDisabled = tasksConfig?.enabled === false;
58747
+ const tasksExplicitlyEnabled = tasksConfig?.enabled === true;
58508
58748
  const isTaskWorker = !!this.metadata["__taskWorkerMode"];
58509
58749
  const hasPersistentBackend = Boolean(
58510
58750
  tasksConfig?.sqlite || tasksConfig?.redis || this.metadata.redis || this.metadata.sqlite
58511
58751
  );
58512
58752
  const tasksEnabledForCli = this.cliMode && hasPersistentBackend;
58513
- const shouldInitTasks = !tasksExplicitlyDisabled && (!this.cliMode || tasksEnabledForCli || isTaskWorker);
58753
+ const hasDistributedTaskBackend = Boolean(tasksConfig?.redis ?? this.metadata.redis);
58754
+ const onEdgeRuntime = isEdgeRuntime();
58755
+ const tasksUnavailableOnEdge = onEdgeRuntime && !hasDistributedTaskBackend && !tasksExplicitlyEnabled;
58756
+ if (tasksUnavailableOnEdge) {
58757
+ this.logger.info(
58758
+ "[tasks] Background tasks are unavailable on this edge/serverless runtime: the in-process runner cannot continue past the HTTP response, and an in-memory store is not shared between isolates. Serving without tasks. Configure `tasks.redis` (Upstash Redis over HTTP works on Workers) to enable them."
58759
+ );
58760
+ }
58761
+ const shouldInitTasks = !tasksExplicitlyDisabled && !tasksUnavailableOnEdge && (!this.cliMode || tasksEnabledForCli || isTaskWorker);
58514
58762
  const tasksPromise = shouldInitTasks ? (async () => {
58515
58763
  const tasksRedis = tasksConfig?.redis ?? this.metadata.redis;
58516
58764
  const tasksSqlite = tasksConfig?.sqlite ?? (tasksRedis ? void 0 : resolvedTopLevelSqlite);
58517
- const onEdge = isEdgeRuntime();
58765
+ const onEdge = onEdgeRuntime;
58518
58766
  if (onEdge) {
58519
58767
  const msg = "[tasks] Background tasks are enabled on an edge/serverless runtime. The in-process runner cannot continue past the HTTP response on this platform \u2014 task bodies will not execute. Run on a long-lived Node process, or set `tasks.enabled: false`.";
58520
58768
  if (tasksConfig?.strict) {
@@ -60660,7 +60908,7 @@ function createWebFetchHandler(scope, options = {}) {
60660
60908
  while (end > 1 && withSlash.charCodeAt(end - 1) === 47) end--;
60661
60909
  return withSlash.slice(0, end);
60662
60910
  };
60663
- const rawEntry = options.entryPath ?? (normalizeEntryPrefix(httpConfig?.entryPath) || "/");
60911
+ const rawEntry = options.entryPath ?? (normalizeEntryPrefix(resolveEntryPath(httpConfig?.entryPath)) || "/");
60664
60912
  const entryPaths = new Set((Array.isArray(rawEntry) ? rawEntry : [rawEntry]).map(normalizePath));
60665
60913
  const cors2 = options.cors ?? mapHttpCors(httpConfig?.cors, scope);
60666
60914
  const corsEnabled = cors2?.origin !== void 0 && cors2.origin !== false;
@@ -60725,7 +60973,7 @@ function createWebFetchHandler(scope, options = {}) {
60725
60973
  );
60726
60974
  }
60727
60975
  if (!entryPaths.has(normalizePath(url.pathname))) {
60728
- const authResponse = await runMatchingHttpFlowWeb(scope, request);
60976
+ const authResponse = await runMatchingHttpFlowWeb(scope, request, { ctx, env });
60729
60977
  if (authResponse) return withCors(authResponse, request);
60730
60978
  return withCors(Response.json({ error: "Not Found", entryPaths: [...entryPaths] }, { status: 404 }), request);
60731
60979
  }
@@ -60733,13 +60981,13 @@ function createWebFetchHandler(scope, options = {}) {
60733
60981
  const routed = await options.sessionRouter(request, env, ctx);
60734
60982
  if (routed) return withCors(routed, request);
60735
60983
  }
60736
- const rendered = await runHttpRequestFlowWeb(scope, request, { ctx });
60984
+ const rendered = await runHttpRequestFlowWeb(scope, request, { ctx, env });
60737
60985
  return withCors(rendered ?? Response.json({ error: "Not Found" }, { status: 404 }), request);
60738
60986
  };
60739
60987
  }
60740
60988
  async function runHttpRequestFlowWeb(scope, request, opts = {}) {
60741
60989
  const url = new URL(request.url);
60742
- const serverRequest = await toServerRequest(request, url, opts.ctx, opts.persistent);
60990
+ const serverRequest = await toServerRequest(request, url, opts.ctx, opts.persistent, opts.env);
60743
60991
  let output;
60744
60992
  try {
60745
60993
  output = await scope.runFlow("http:request", {
@@ -60751,9 +60999,9 @@ async function runHttpRequestFlowWeb(scope, request, opts = {}) {
60751
60999
  }
60752
61000
  return output ? renderHttpOutputToWebResponse(output) : void 0;
60753
61001
  }
60754
- async function runMatchingHttpFlowWeb(scope, request) {
61002
+ async function runMatchingHttpFlowWeb(scope, request, opts = {}) {
60755
61003
  const url = new URL(request.url);
60756
- const serverRequest = await toServerRequest(request, url);
61004
+ const serverRequest = await toServerRequest(request, url, opts.ctx, void 0, opts.env);
60757
61005
  const flowName = await scope.findHttpFlowName(serverRequest);
60758
61006
  if (!flowName) return void 0;
60759
61007
  let output;
@@ -60767,7 +61015,7 @@ async function runMatchingHttpFlowWeb(scope, request) {
60767
61015
  }
60768
61016
  return output ? renderHttpOutputToWebResponse(output) : void 0;
60769
61017
  }
60770
- async function toServerRequest(request, url, ctx, persistent) {
61018
+ async function toServerRequest(request, url, ctx, persistent, env) {
60771
61019
  const headers = {};
60772
61020
  request.headers.forEach((v, k) => {
60773
61021
  headers[k] = v;
@@ -60811,10 +61059,39 @@ async function toServerRequest(request, url, ctx, persistent) {
60811
61059
  const tokenized = serverRequest;
60812
61060
  tokenized[ServerRequestTokens.webRequest] = webRequest;
60813
61061
  tokenized[ServerRequestTokens.webCtx] = ctx;
61062
+ tokenized[ServerRequestTokens.webEnv] = env;
60814
61063
  if (persistent) tokenized[ServerRequestTokens.webTransport] = persistent;
60815
61064
  return serverRequest;
60816
61065
  }
61066
+ function misconfigurationResponse(error) {
61067
+ const misconfiguration = findMisconfiguration(error);
61068
+ if (!misconfiguration) return void 0;
61069
+ return Response.json(
61070
+ { error: "server_misconfigured", code: misconfiguration.code, message: misconfiguration.remedy },
61071
+ { status: 500 }
61072
+ );
61073
+ }
61074
+ function findMisconfiguration(error) {
61075
+ let current = error;
61076
+ for (let depth = 0; current instanceof Error && depth < 5; depth++) {
61077
+ const code = current.code;
61078
+ if (typeof code === "string" && MISCONFIGURATION_REMEDIES[code]) {
61079
+ return { code, remedy: MISCONFIGURATION_REMEDIES[code] };
61080
+ }
61081
+ current = current.cause;
61082
+ }
61083
+ return void 0;
61084
+ }
60817
61085
  function flowErrorToHttpOutput(error) {
61086
+ const misconfiguration = findMisconfiguration(error);
61087
+ if (misconfiguration) {
61088
+ return {
61089
+ kind: "json",
61090
+ status: 500,
61091
+ contentType: "application/json; charset=utf-8",
61092
+ body: { error: "server_misconfigured", code: misconfiguration.code, message: misconfiguration.remedy }
61093
+ };
61094
+ }
60818
61095
  if (error instanceof FlowControl) {
60819
61096
  switch (error.type) {
60820
61097
  case "respond":
@@ -60838,6 +61115,7 @@ function flowErrorToHttpOutput(error) {
60838
61115
  }
60839
61116
  return { kind: "text", status: 500, body: "Internal Server Error", contentType: "text/plain; charset=utf-8" };
60840
61117
  }
61118
+ var MISCONFIGURATION_REMEDIES;
60841
61119
  var init_web_fetch_handler = __esm({
60842
61120
  "libs/sdk/src/transport/web-fetch-handler.ts"() {
60843
61121
  "use strict";
@@ -60847,6 +61125,11 @@ var init_web_fetch_handler = __esm({
60847
61125
  init_errors();
60848
61126
  init_host_validation();
60849
61127
  init_web_response_renderer();
61128
+ MISCONFIGURATION_REMEDIES = {
61129
+ 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.",
61130
+ 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`.",
61131
+ 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."
61132
+ };
60850
61133
  }
60851
61134
  });
60852
61135
 
@@ -61200,17 +61483,16 @@ var init_express_host_adapter = __esm({
61200
61483
  });
61201
61484
  } else {
61202
61485
  this.deriveHostValidation = (portOrSocketPath, bindAddress) => {
61203
- const isSocket = typeof portOrSocketPath === "string";
61486
+ if (typeof portOrSocketPath === "string") return void 0;
61204
61487
  const derivation = {
61205
61488
  // `start()` binds `bindAddress ?? '0.0.0.0'`; mirror that exactly.
61206
- bindAddress: isSocket ? void 0 : bindAddress ?? "0.0.0.0",
61207
- port: isSocket ? void 0 : portOrSocketPath,
61208
- socketPath: isSocket ? portOrSocketPath : void 0,
61489
+ bindAddress: bindAddress ?? "0.0.0.0",
61490
+ port: portOrSocketPath,
61209
61491
  issuer: options?.listen?.issuer
61210
61492
  };
61211
61493
  if (!shouldEnforceDerivedHosts(derivation)) {
61212
61494
  console.warn(
61213
- `[frontmcp] DNS-rebinding protection is not enforcing a Host allow-list: the server binds a routable address (${derivation.bindAddress ?? "unknown"}) and no allowed hosts are configured. Set security.dnsRebindingProtection.allowedHosts (or FRONTMCP_ALLOWED_HOSTS) to your public hostname(s).`
61495
+ `[frontmcp] DNS-rebinding protection is not enforcing a Host allow-list: the server binds a routable address (${derivation.bindAddress}) and no allowed hosts are configured. Set security.dnsRebindingProtection.allowedHosts (or FRONTMCP_ALLOWED_HOSTS) to your public hostname(s).`
61214
61496
  );
61215
61497
  return void 0;
61216
61498
  }
@@ -61266,7 +61548,6 @@ var init_express_host_adapter = __esm({
61266
61548
  return this.app;
61267
61549
  }
61268
61550
  async start(portOrSocketPath, bindAddress) {
61269
- this.enableDerivedHostValidation(portOrSocketPath, bindAddress);
61270
61551
  this.prepare();
61271
61552
  const server = http.createServer(this.app);
61272
61553
  server.requestTimeout = 0;
@@ -61274,6 +61555,7 @@ var init_express_host_adapter = __esm({
61274
61555
  server.keepAliveTimeout = 75e3;
61275
61556
  if (typeof portOrSocketPath === "string") {
61276
61557
  await this.cleanupStaleSocket(portOrSocketPath);
61558
+ this.enableDerivedHostValidation(portOrSocketPath);
61277
61559
  await new Promise((resolve, reject) => {
61278
61560
  server.on("error", reject);
61279
61561
  server.listen(portOrSocketPath, () => {
@@ -61291,7 +61573,11 @@ var init_express_host_adapter = __esm({
61291
61573
  await new Promise((resolve, reject) => {
61292
61574
  server.on("error", reject);
61293
61575
  server.listen(portOrSocketPath, host, () => {
61294
- console.log(`MCP HTTP (Express) on ${host}:${portOrSocketPath}`);
61576
+ const bound = server.address();
61577
+ const boundPort = typeof bound === "object" && bound ? bound.port : portOrSocketPath;
61578
+ const boundAddress = typeof bound === "object" && bound ? bound.address : host;
61579
+ this.enableDerivedHostValidation(boundPort, boundAddress);
61580
+ console.log(`MCP HTTP (Express) on ${host}:${boundPort}`);
61295
61581
  resolve();
61296
61582
  });
61297
61583
  });
@@ -61693,7 +61979,7 @@ var init_front_mcp = __esm({
61693
61979
  frontMcp.log?.info("FrontMCP fetch handler created (web-standard transport)");
61694
61980
  return createWebFetchHandler(scope);
61695
61981
  };
61696
- return async (request) => {
61982
+ return async (request, ctx, env) => {
61697
61983
  if (!inner) {
61698
61984
  if (!building) {
61699
61985
  building = build().catch((err) => {
@@ -61701,9 +61987,15 @@ var init_front_mcp = __esm({
61701
61987
  throw err;
61702
61988
  });
61703
61989
  }
61704
- inner = await building;
61990
+ try {
61991
+ inner = await building;
61992
+ } catch (err) {
61993
+ const misconfigured = misconfigurationResponse(err);
61994
+ if (misconfigured) return misconfigured;
61995
+ throw err;
61996
+ }
61705
61997
  }
61706
- return inner(request);
61998
+ return inner(request, ctx, env);
61707
61999
  };
61708
62000
  }
61709
62001
  /**
@@ -64685,6 +64977,8 @@ export {
64685
64977
  JobNotAuthorizedError,
64686
64978
  JobPermissionGuard,
64687
64979
  JobRegistry,
64980
+ JwtSecretRequiredError,
64981
+ JwtSecretWeakError,
64688
64982
  LLM_ADAPTER,
64689
64983
  LazyZodSchema,
64690
64984
  ListJobsTool,
@@ -65112,6 +65406,7 @@ export {
65112
65406
  isOrchestratedMode,
65113
65407
  isOrchestratedRemote,
65114
65408
  isPackageSpecifier,
65409
+ isProxyTrusted,
65115
65410
  isPublicError,
65116
65411
  isPublicMode,
65117
65412
  isPublicUrlPinned,
@@ -65120,6 +65415,7 @@ export {
65120
65415
  isRemoteMode,
65121
65416
  isSkillDecorated,
65122
65417
  isSkillRecord,
65418
+ isStaticMode,
65123
65419
  isTaskWorkerProcess,
65124
65420
  isTerminal,
65125
65421
  isToolRefWithClass,
@@ -65197,6 +65493,7 @@ export {
65197
65493
  remoteAuthSchema,
65198
65494
  remoteProviderConfigSchema,
65199
65495
  resolveDefaultSqlitePath,
65496
+ resolveEntryPath,
65200
65497
  resolveNameConflicts,
65201
65498
  resolveOutputSchemaMode,
65202
65499
  resolvePendingTC39HooksForClass,
@@ -65239,6 +65536,7 @@ export {
65239
65536
  skillsConfigOptionsSchema,
65240
65537
  skippedAppBehaviorSchema,
65241
65538
  sqliteOptionsSchema,
65539
+ staticAuthOptionsSchema,
65242
65540
  storageProviderSchema,
65243
65541
  supportsStreaming,
65244
65542
  toJSONSchema9 as toJSONSchema,