@frontmcp/sdk 1.8.6 → 1.8.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agent/flows/call-agent.flow.d.ts.map +1 -1
- package/auth/instances/instance.local-primary-auth.d.ts.map +1 -1
- package/common/dynamic/dynamic.adapter.d.ts +1 -1
- package/common/dynamic/dynamic.adapter.d.ts.map +1 -1
- package/common/dynamic/dynamic.plugin.d.ts +7 -1
- package/common/dynamic/dynamic.plugin.d.ts.map +1 -1
- package/common/dynamic/dynamic.utils.d.ts +10 -1
- package/common/dynamic/dynamic.utils.d.ts.map +1 -1
- package/common/interfaces/execution-context.interface.d.ts +14 -0
- package/common/interfaces/execution-context.interface.d.ts.map +1 -1
- package/common/metadata/front-mcp.metadata.d.ts +127 -19
- package/common/metadata/front-mcp.metadata.d.ts.map +1 -1
- package/common/types/options/http/index.d.ts +1 -1
- package/common/types/options/http/index.d.ts.map +1 -1
- package/common/types/options/http/interfaces.d.ts +36 -1
- package/common/types/options/http/interfaces.d.ts.map +1 -1
- package/common/types/options/http/schema.d.ts +12 -0
- package/common/types/options/http/schema.d.ts.map +1 -1
- package/common/types/options/transport/schema.d.ts +2 -2
- package/context/frontmcp-context-storage.d.ts.map +1 -1
- package/context/frontmcp-context.d.ts +11 -0
- package/context/frontmcp-context.d.ts.map +1 -1
- package/elicitation/store/elicitation-store.factory.d.ts.map +1 -1
- package/errors/misconfiguration.d.ts +30 -0
- package/errors/misconfiguration.d.ts.map +1 -0
- package/esm/index.mjs +912 -198
- package/flows/flow.instance.d.ts.map +1 -1
- package/front-mcp/front-mcp.d.ts.map +1 -1
- package/ha/ha-headers.d.ts +16 -0
- package/ha/ha-headers.d.ts.map +1 -0
- package/health/health.probes.d.ts +1 -1
- package/health/health.probes.d.ts.map +1 -1
- package/index.js +1016 -298
- package/job/store/job-definition-store.factory.d.ts.map +1 -1
- package/job/store/job-state-store.factory.d.ts.map +1 -1
- package/notification/notification.service.d.ts +4 -1
- package/notification/notification.service.d.ts.map +1 -1
- package/package.json +10 -10
- package/remote-mcp/mcp-stateless-client.adapter.d.ts +9 -1
- package/remote-mcp/mcp-stateless-client.adapter.d.ts.map +1 -1
- package/remote-mcp/resilience/health-check.d.ts.map +1 -1
- package/scope/scope.instance.d.ts +8 -0
- package/scope/scope.instance.d.ts.map +1 -1
- package/server/adapters/express.host.adapter.d.ts +6 -1
- package/server/adapters/express.host.adapter.d.ts.map +1 -1
- package/server/middleware/csp.middleware.d.ts +12 -1
- package/server/middleware/csp.middleware.d.ts.map +1 -1
- package/server/security/security-audit.d.ts.map +1 -1
- package/server/server.instance.d.ts.map +1 -1
- package/skill/cache/skill-http-cache.factory.d.ts +0 -5
- package/skill/cache/skill-http-cache.factory.d.ts.map +1 -1
- package/task/store/index.d.ts +1 -1
- package/task/store/index.d.ts.map +1 -1
- package/task/store/task-store.factory.d.ts +6 -0
- package/task/store/task-store.factory.d.ts.map +1 -1
- package/tool/flows/call-tool.flow.d.ts.map +1 -1
- package/tool/ui/ui-option-warnings.d.ts +3 -0
- package/tool/ui/ui-option-warnings.d.ts.map +1 -0
- package/tool/ui/ui-resource.handler.d.ts.map +1 -1
- package/tool/ui/ui-shared.d.ts +14 -0
- package/tool/ui/ui-shared.d.ts.map +1 -1
- package/transport/event-stores/redis.event-store.d.ts.map +1 -1
- package/transport/flows/handle.mcp-20260728.flow.d.ts.map +1 -1
- package/transport/flows/handle.sse.flow.d.ts.map +1 -1
- package/transport/flows/handle.stateless-http.flow.d.ts +2 -1
- package/transport/flows/handle.stateless-http.flow.d.ts.map +1 -1
- package/transport/flows/handle.streamable-http.flow.d.ts +2 -1
- package/transport/flows/handle.streamable-http.flow.d.ts.map +1 -1
- package/transport/ipc-server.transport.d.ts +26 -0
- package/transport/ipc-server.transport.d.ts.map +1 -0
- package/transport/mcp-20260728/client/mcp-stateless.client.d.ts +2 -0
- package/transport/mcp-20260728/client/mcp-stateless.client.d.ts.map +1 -1
- package/transport/transport.registry.d.ts +34 -0
- package/transport/transport.registry.d.ts.map +1 -1
- package/transport/web-fetch-handler.d.ts.map +1 -1
package/esm/index.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
const require =
|
|
1
|
+
const __frontmcpModule = typeof process !== "undefined" && typeof process.getBuiltinModule === "function" ? process.getBuiltinModule("module") : undefined;
|
|
2
|
+
const require = __frontmcpModule ? __frontmcpModule.createRequire(import.meta.url || 'file:///') : undefined;
|
|
3
|
+
// __frontmcpCreateRequire
|
|
3
4
|
var __defProp = Object.defineProperty;
|
|
4
5
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
6
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
@@ -1861,7 +1862,7 @@ var init_session = __esm({
|
|
|
1861
1862
|
|
|
1862
1863
|
// libs/sdk/src/common/types/options/http/schema.ts
|
|
1863
1864
|
import { z as z8 } from "@frontmcp/lazy-zod";
|
|
1864
|
-
var HTTP_METHODS, httpRouteSchema, bodyParserLimitSchema, corsOptionsSchema, httpOptionsSchema;
|
|
1865
|
+
var HTTP_METHODS, httpRouteSchema, bodyParserLimitSchema, corsOptionsSchema, securityHeaderValueSchema, securityHeadersSchema, httpOptionsSchema;
|
|
1865
1866
|
var init_schema3 = __esm({
|
|
1866
1867
|
"libs/sdk/src/common/types/options/http/schema.ts"() {
|
|
1867
1868
|
"use strict";
|
|
@@ -1898,6 +1899,19 @@ var init_schema3 = __esm({
|
|
|
1898
1899
|
credentials: z8.boolean().optional(),
|
|
1899
1900
|
maxAge: z8.number().optional()
|
|
1900
1901
|
});
|
|
1902
|
+
securityHeaderValueSchema = z8.union([z8.string(), z8.literal(false)]);
|
|
1903
|
+
securityHeadersSchema = z8.object({
|
|
1904
|
+
hsts: securityHeaderValueSchema.optional(),
|
|
1905
|
+
contentTypeOptions: securityHeaderValueSchema.optional(),
|
|
1906
|
+
frameOptions: securityHeaderValueSchema.optional(),
|
|
1907
|
+
custom: z8.record(z8.string(), z8.string()).optional(),
|
|
1908
|
+
csp: z8.object({
|
|
1909
|
+
enabled: z8.boolean().optional(),
|
|
1910
|
+
directives: z8.record(z8.string(), z8.union([z8.string(), z8.array(z8.string())])).optional(),
|
|
1911
|
+
reportUri: z8.string().optional(),
|
|
1912
|
+
reportOnly: z8.boolean().optional()
|
|
1913
|
+
}).optional()
|
|
1914
|
+
});
|
|
1901
1915
|
httpOptionsSchema = z8.object({
|
|
1902
1916
|
port: z8.number().optional().default(Number(process.env["PORT"]) || 3e3),
|
|
1903
1917
|
// `FRONTMCP_HTTP_ENTRY_PATH` lets `frontmcp dev` propagate the configured
|
|
@@ -1956,7 +1970,9 @@ var init_schema3 = __esm({
|
|
|
1956
1970
|
* app as the MCP endpoint; public by default, `auth: true` opts into the MCP
|
|
1957
1971
|
* `session:verify` flow. Reserved-path collisions are rejected at startup.
|
|
1958
1972
|
*/
|
|
1959
|
-
routes: z8.array(httpRouteSchema).optional()
|
|
1973
|
+
routes: z8.array(httpRouteSchema).optional(),
|
|
1974
|
+
/** Response security headers; see `SecurityHeadersOptions`. */
|
|
1975
|
+
securityHeaders: securityHeadersSchema.optional()
|
|
1960
1976
|
});
|
|
1961
1977
|
}
|
|
1962
1978
|
});
|
|
@@ -2394,7 +2410,7 @@ var init_schema7 = __esm({
|
|
|
2394
2410
|
persistenceConfigSchema = z12.object({
|
|
2395
2411
|
redis: redisOptionsSchema.optional(),
|
|
2396
2412
|
sqlite: sqliteOptionsSchema.optional(),
|
|
2397
|
-
defaultTtlMs: z12.number().int().positive().
|
|
2413
|
+
defaultTtlMs: z12.number().int().positive().optional()
|
|
2398
2414
|
}).refine((cfg) => !(cfg.redis && cfg.sqlite), {
|
|
2399
2415
|
message: "Invalid persistence config: choose either redis or sqlite, not both",
|
|
2400
2416
|
path: ["sqlite"]
|
|
@@ -6510,6 +6526,7 @@ var require_dist = __commonJS({
|
|
|
6510
6526
|
var __toCommonJS2 = (mod) => __copyProps2(__defProp2({}, "__esModule", { value: true }), mod);
|
|
6511
6527
|
var index_exports = {};
|
|
6512
6528
|
__export2(index_exports, {
|
|
6529
|
+
SqliteDecryptionError: () => SqliteDecryptionError,
|
|
6513
6530
|
SqliteElicitationStore: () => SqliteElicitationStore,
|
|
6514
6531
|
SqliteEventStore: () => SqliteEventStore,
|
|
6515
6532
|
SqliteKvStore: () => SqliteKvStore,
|
|
@@ -6521,34 +6538,47 @@ var require_dist = __commonJS({
|
|
|
6521
6538
|
encryptValue: () => encryptValue,
|
|
6522
6539
|
loadBetterSqlite3: () => loadBetterSqlite3,
|
|
6523
6540
|
openDatabase: () => openDatabase,
|
|
6524
|
-
sqliteStorageOptionsSchema: () => sqliteStorageOptionsSchema
|
|
6541
|
+
sqliteStorageOptionsSchema: () => sqliteStorageOptionsSchema,
|
|
6542
|
+
withBusyRetry: () => withBusyRetry
|
|
6525
6543
|
});
|
|
6526
6544
|
module.exports = __toCommonJS2(index_exports);
|
|
6527
|
-
var
|
|
6545
|
+
var import_utils119 = __require("@frontmcp/utils");
|
|
6546
|
+
var SqliteDecryptionError = class extends Error {
|
|
6547
|
+
code = "SQLITE_DECRYPTION_FAILED";
|
|
6548
|
+
constructor() {
|
|
6549
|
+
super(
|
|
6550
|
+
"SqliteKvStore: failed to decrypt a stored value. The encryption secret differs from the one the database was written with (or the value is corrupted). Restore the original secret, or delete the database file to start empty."
|
|
6551
|
+
);
|
|
6552
|
+
this.name = "SqliteDecryptionError";
|
|
6553
|
+
}
|
|
6554
|
+
};
|
|
6528
6555
|
var HKDF_SALT = new TextEncoder().encode("frontmcp-sqlite-storage-v1");
|
|
6529
6556
|
var HKDF_INFO = new TextEncoder().encode("aes-256-gcm-value-encryption");
|
|
6530
6557
|
var KEY_LENGTH = 32;
|
|
6531
6558
|
function deriveEncryptionKey(secret) {
|
|
6532
6559
|
const ikm = new TextEncoder().encode(secret);
|
|
6533
|
-
return (0,
|
|
6560
|
+
return (0, import_utils119.hkdfSha256)(ikm, HKDF_SALT, HKDF_INFO, KEY_LENGTH);
|
|
6534
6561
|
}
|
|
6535
6562
|
var SEPARATOR = ":";
|
|
6536
6563
|
function encryptValue(key, plaintext) {
|
|
6537
|
-
const iv = (0,
|
|
6564
|
+
const iv = (0, import_utils119.randomBytes)(12);
|
|
6538
6565
|
const plaintextBytes = new TextEncoder().encode(plaintext);
|
|
6539
|
-
const { ciphertext, tag } = (0,
|
|
6540
|
-
return [(0,
|
|
6566
|
+
const { ciphertext, tag } = (0, import_utils119.encryptAesGcm)(key, plaintextBytes, iv);
|
|
6567
|
+
return [(0, import_utils119.base64urlEncode)(iv), (0, import_utils119.base64urlEncode)(tag), (0, import_utils119.base64urlEncode)(ciphertext)].join(SEPARATOR);
|
|
6541
6568
|
}
|
|
6542
6569
|
function decryptValue(key, encrypted) {
|
|
6543
6570
|
const parts = encrypted.split(SEPARATOR);
|
|
6544
6571
|
if (parts.length !== 3) {
|
|
6545
6572
|
throw new Error("Invalid encrypted value format");
|
|
6546
6573
|
}
|
|
6547
|
-
const iv = (0,
|
|
6548
|
-
const tag = (0,
|
|
6549
|
-
const ciphertext = (0,
|
|
6550
|
-
|
|
6551
|
-
|
|
6574
|
+
const iv = (0, import_utils119.base64urlDecode)(parts[0]);
|
|
6575
|
+
const tag = (0, import_utils119.base64urlDecode)(parts[1]);
|
|
6576
|
+
const ciphertext = (0, import_utils119.base64urlDecode)(parts[2]);
|
|
6577
|
+
try {
|
|
6578
|
+
return new TextDecoder().decode((0, import_utils119.decryptAesGcm)(key, ciphertext, iv, tag));
|
|
6579
|
+
} catch {
|
|
6580
|
+
throw new SqliteDecryptionError();
|
|
6581
|
+
}
|
|
6552
6582
|
}
|
|
6553
6583
|
var import_utils210 = __require("@frontmcp/utils");
|
|
6554
6584
|
var cached = null;
|
|
@@ -6561,7 +6591,27 @@ var require_dist = __commonJS({
|
|
|
6561
6591
|
return mod;
|
|
6562
6592
|
}
|
|
6563
6593
|
var MEMORY_PATH = ":memory:";
|
|
6564
|
-
|
|
6594
|
+
var DEFAULT_BUSY_TIMEOUT_MS = 5e3;
|
|
6595
|
+
function isBusyError(err) {
|
|
6596
|
+
const code = err?.code;
|
|
6597
|
+
return code === "SQLITE_BUSY" || code === "SQLITE_BUSY_RECOVERY" || code === "SQLITE_BUSY_SNAPSHOT";
|
|
6598
|
+
}
|
|
6599
|
+
function sleepSync(ms) {
|
|
6600
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
6601
|
+
}
|
|
6602
|
+
function withBusyRetry(operation, options = {}) {
|
|
6603
|
+
const attempts = options.attempts ?? 5;
|
|
6604
|
+
const delayMs = options.delayMs ?? 50;
|
|
6605
|
+
for (let attempt = 1; ; attempt++) {
|
|
6606
|
+
try {
|
|
6607
|
+
return operation();
|
|
6608
|
+
} catch (err) {
|
|
6609
|
+
if (!isBusyError(err) || attempt >= attempts) throw err;
|
|
6610
|
+
sleepSync(delayMs * attempt);
|
|
6611
|
+
}
|
|
6612
|
+
}
|
|
6613
|
+
}
|
|
6614
|
+
function openDatabase(path3, storeName, options = {}) {
|
|
6565
6615
|
const BetterSqlite3 = loadBetterSqlite3();
|
|
6566
6616
|
if (path3 !== MEMORY_PATH && path3 !== "") {
|
|
6567
6617
|
const dir = (0, import_utils210.dirname)(path3);
|
|
@@ -6570,16 +6620,28 @@ var require_dist = __commonJS({
|
|
|
6570
6620
|
(0, import_utils210.ensureDirSync)(dir);
|
|
6571
6621
|
} catch (err) {
|
|
6572
6622
|
const message = err instanceof Error ? err.message : String(err);
|
|
6573
|
-
throw new Error(`${storeName}: failed to create directory "${dir}" for database "${path3}": ${message}
|
|
6623
|
+
throw new Error(`${storeName}: failed to create directory "${dir}" for database "${path3}": ${message}`, {
|
|
6624
|
+
cause: err
|
|
6625
|
+
});
|
|
6574
6626
|
}
|
|
6575
6627
|
}
|
|
6576
6628
|
}
|
|
6629
|
+
let db;
|
|
6630
|
+
try {
|
|
6631
|
+
db = new BetterSqlite3(path3);
|
|
6632
|
+
} catch (err) {
|
|
6633
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
6634
|
+
throw new Error(`${storeName}: failed to open database at "${path3}": ${message}`, { cause: err });
|
|
6635
|
+
}
|
|
6577
6636
|
try {
|
|
6578
|
-
|
|
6637
|
+
db.pragma(`busy_timeout = ${Math.max(0, Math.floor(options.busyTimeoutMs ?? DEFAULT_BUSY_TIMEOUT_MS))}`);
|
|
6638
|
+
if (options.walMode) withBusyRetry(() => db.pragma("journal_mode = WAL"));
|
|
6579
6639
|
} catch (err) {
|
|
6640
|
+
db.close();
|
|
6580
6641
|
const message = err instanceof Error ? err.message : String(err);
|
|
6581
|
-
throw new Error(`${storeName}: failed to
|
|
6642
|
+
throw new Error(`${storeName}: failed to configure database at "${path3}": ${message}`, { cause: err });
|
|
6582
6643
|
}
|
|
6644
|
+
return db;
|
|
6583
6645
|
}
|
|
6584
6646
|
var SqliteKvStore = class {
|
|
6585
6647
|
db;
|
|
@@ -6587,14 +6649,14 @@ var require_dist = __commonJS({
|
|
|
6587
6649
|
cleanupTimer = null;
|
|
6588
6650
|
stmts = null;
|
|
6589
6651
|
constructor(options) {
|
|
6590
|
-
this.db = openDatabase(options.path, "SqliteKvStore"
|
|
6591
|
-
|
|
6592
|
-
|
|
6593
|
-
}
|
|
6652
|
+
this.db = openDatabase(options.path, "SqliteKvStore", {
|
|
6653
|
+
busyTimeoutMs: options.busyTimeoutMs,
|
|
6654
|
+
walMode: options.walMode !== false
|
|
6655
|
+
});
|
|
6594
6656
|
if (options.encryption?.secret) {
|
|
6595
6657
|
this.encryptionKey = deriveEncryptionKey(options.encryption.secret);
|
|
6596
6658
|
}
|
|
6597
|
-
this.initSchema();
|
|
6659
|
+
withBusyRetry(() => this.initSchema());
|
|
6598
6660
|
this.prepareStatements();
|
|
6599
6661
|
const cleanupInterval = options.ttlCleanupIntervalMs ?? 6e4;
|
|
6600
6662
|
if (cleanupInterval > 0) {
|
|
@@ -7053,17 +7115,17 @@ var require_dist = __commonJS({
|
|
|
7053
7115
|
encryptionKey = null;
|
|
7054
7116
|
stmts = null;
|
|
7055
7117
|
constructor(options) {
|
|
7056
|
-
this.db = openDatabase(options.path, "SqliteTaskStore"
|
|
7057
|
-
|
|
7058
|
-
|
|
7059
|
-
}
|
|
7118
|
+
this.db = openDatabase(options.path, "SqliteTaskStore", {
|
|
7119
|
+
busyTimeoutMs: options.busyTimeoutMs,
|
|
7120
|
+
walMode: options.walMode !== false
|
|
7121
|
+
});
|
|
7060
7122
|
this.logger = options.logger;
|
|
7061
7123
|
this.liveness = options.livenessProbe ?? defaultLivenessProbe;
|
|
7062
7124
|
if (options.encryption?.secret) {
|
|
7063
7125
|
this.encryptionKey = deriveEncryptionKey(options.encryption.secret);
|
|
7064
7126
|
}
|
|
7065
7127
|
this.emitter.setMaxListeners(200);
|
|
7066
|
-
this.initSchema();
|
|
7128
|
+
withBusyRetry(() => this.initSchema());
|
|
7067
7129
|
this.prepareStatements();
|
|
7068
7130
|
const cleanupInterval = options.ttlCleanupIntervalMs ?? 6e4;
|
|
7069
7131
|
if (cleanupInterval > 0) {
|
|
@@ -7300,13 +7362,13 @@ var require_dist = __commonJS({
|
|
|
7300
7362
|
cleanupTimer = null;
|
|
7301
7363
|
stmts = null;
|
|
7302
7364
|
constructor(options) {
|
|
7303
|
-
this.db = openDatabase(options.path, "SqliteEventStore"
|
|
7304
|
-
|
|
7305
|
-
|
|
7306
|
-
}
|
|
7365
|
+
this.db = openDatabase(options.path, "SqliteEventStore", {
|
|
7366
|
+
busyTimeoutMs: options.busyTimeoutMs,
|
|
7367
|
+
walMode: options.walMode !== false
|
|
7368
|
+
});
|
|
7307
7369
|
this.maxEvents = options.maxEvents ?? 1e4;
|
|
7308
7370
|
this.ttlMs = options.ttlMs ?? 3e5;
|
|
7309
|
-
this.initSchema();
|
|
7371
|
+
withBusyRetry(() => this.initSchema());
|
|
7310
7372
|
this.prepareStatements();
|
|
7311
7373
|
const cleanupInterval = options.ttlCleanupIntervalMs ?? 6e4;
|
|
7312
7374
|
if (cleanupInterval > 0) {
|
|
@@ -7441,7 +7503,8 @@ var require_dist = __commonJS({
|
|
|
7441
7503
|
secret: import_lazy_zod113.z.string().min(1)
|
|
7442
7504
|
}).optional(),
|
|
7443
7505
|
ttlCleanupIntervalMs: import_lazy_zod113.z.number().int().nonnegative().optional().default(6e4),
|
|
7444
|
-
walMode: import_lazy_zod113.z.boolean().optional().default(true)
|
|
7506
|
+
walMode: import_lazy_zod113.z.boolean().optional().default(true),
|
|
7507
|
+
busyTimeoutMs: import_lazy_zod113.z.number().int().nonnegative().optional().default(5e3)
|
|
7445
7508
|
});
|
|
7446
7509
|
}
|
|
7447
7510
|
});
|
|
@@ -7460,6 +7523,14 @@ function detectStorageType(storage, configuredType) {
|
|
|
7460
7523
|
if (getEnv("REDIS_URL") || getEnv("REDIS_HOST")) return "redis";
|
|
7461
7524
|
return "memory";
|
|
7462
7525
|
}
|
|
7526
|
+
function isExplicitStorage(storage) {
|
|
7527
|
+
return Boolean(storage?.type && storage.type !== "auto");
|
|
7528
|
+
}
|
|
7529
|
+
function resolvesToVercelKvTaskBackend(redis, storage) {
|
|
7530
|
+
if (isExplicitStorage(storage)) return storage.type === "vercel-kv";
|
|
7531
|
+
if (redis) return "provider" in redis && redis.provider === "vercel-kv";
|
|
7532
|
+
return Boolean(getEnv("KV_REST_API_URL"));
|
|
7533
|
+
}
|
|
7463
7534
|
async function createTaskStore(options = {}) {
|
|
7464
7535
|
const { storage: storageConfig, redis, keyPrefix = "mcp:task:", logger, isEdgeRuntime: isEdgeRuntime3 = false, sqlite } = options;
|
|
7465
7536
|
if (isEdgeRuntime3 && sqlite) {
|
|
@@ -7484,7 +7555,7 @@ async function createTaskStore(options = {}) {
|
|
|
7484
7555
|
return { store: store2, type: "sqlite" };
|
|
7485
7556
|
}
|
|
7486
7557
|
let finalStorageConfig = storageConfig;
|
|
7487
|
-
if (redis && !storageConfig) {
|
|
7558
|
+
if (redis && !isExplicitStorage(storageConfig)) {
|
|
7488
7559
|
if ("provider" in redis && redis.provider === "vercel-kv") {
|
|
7489
7560
|
throw new TaskStoreNotSupportedError(
|
|
7490
7561
|
"Vercel KV is not supported for task stores. Task result blocking and cancel signalling require pub/sub. Use Redis or Upstash instead."
|
|
@@ -7512,7 +7583,8 @@ async function createTaskStore(options = {}) {
|
|
|
7512
7583
|
};
|
|
7513
7584
|
}
|
|
7514
7585
|
}
|
|
7515
|
-
|
|
7586
|
+
const explicitBackend = isExplicitStorage(storageConfig) || Boolean(redis);
|
|
7587
|
+
if (finalStorageConfig?.type === "vercel-kv" || !explicitBackend && getEnv("KV_REST_API_URL")) {
|
|
7516
7588
|
throw new TaskStoreNotSupportedError(
|
|
7517
7589
|
"Vercel KV is not supported for task stores (pub/sub required). Use Redis or Upstash."
|
|
7518
7590
|
);
|
|
@@ -8753,11 +8825,8 @@ import {
|
|
|
8753
8825
|
} from "@frontmcp/protocol";
|
|
8754
8826
|
import { awaitOutsideRequest } from "@frontmcp/utils";
|
|
8755
8827
|
function hasMcpAppsExtension(capabilities) {
|
|
8756
|
-
|
|
8757
|
-
|
|
8758
|
-
}
|
|
8759
|
-
const uiExtension = capabilities.experimental[MCP_APPS_EXTENSION_KEY];
|
|
8760
|
-
return uiExtension !== void 0 && uiExtension !== null;
|
|
8828
|
+
const declared = capabilities?.experimental?.[MCP_APPS_EXTENSION_KEY] ?? capabilities?.extensions?.[MCP_APPS_EXTENSION_KEY];
|
|
8829
|
+
return declared !== void 0 && declared !== null;
|
|
8761
8830
|
}
|
|
8762
8831
|
function detectPlatformFromCapabilities(capabilities) {
|
|
8763
8832
|
if (hasMcpAppsExtension(capabilities)) {
|
|
@@ -11364,6 +11433,8 @@ var init_ui_shared = __esm({
|
|
|
11364
11433
|
widgets = /* @__PURE__ */ new Map();
|
|
11365
11434
|
manifests = /* @__PURE__ */ new Map();
|
|
11366
11435
|
resourceMeta = /* @__PURE__ */ new Map();
|
|
11436
|
+
uiTools = /* @__PURE__ */ new Set();
|
|
11437
|
+
customUris = /* @__PURE__ */ new Map();
|
|
11367
11438
|
resolver;
|
|
11368
11439
|
options;
|
|
11369
11440
|
constructor(resolver, options = {}) {
|
|
@@ -11378,6 +11449,28 @@ var init_ui_shared = __esm({
|
|
|
11378
11449
|
logger: this.options.logger
|
|
11379
11450
|
};
|
|
11380
11451
|
}
|
|
11452
|
+
/**
|
|
11453
|
+
* Record that `toolName` has a UI, and the custom `ui.resourceUri` it advertises (if any).
|
|
11454
|
+
* `tools/list` advertises that URI verbatim, so `resources/read` must resolve it back to the
|
|
11455
|
+
* tool; and only registered tools may be served a placeholder widget.
|
|
11456
|
+
*/
|
|
11457
|
+
registerTool(toolName, resourceUri) {
|
|
11458
|
+
this.uiTools.add(toolName);
|
|
11459
|
+
if (resourceUri) this.customUris.set(resourceUri, toolName);
|
|
11460
|
+
}
|
|
11461
|
+
/** The tool that advertised `uri` as its custom `ui.resourceUri`. */
|
|
11462
|
+
resolveCustomUri(uri) {
|
|
11463
|
+
return this.customUris.get(uri);
|
|
11464
|
+
}
|
|
11465
|
+
/**
|
|
11466
|
+
* Whether `name` (or its app-qualified form `app:name`) is a tool with a UI.
|
|
11467
|
+
*/
|
|
11468
|
+
hasTool(name44) {
|
|
11469
|
+
const known = (n) => this.uiTools.has(n) || this.widgets.has(n) || this.resourceMeta.has(n);
|
|
11470
|
+
if (known(name44)) return true;
|
|
11471
|
+
const sep = name44.indexOf(":");
|
|
11472
|
+
return sep > 0 && known(name44.slice(sep + 1));
|
|
11473
|
+
}
|
|
11381
11474
|
getStaticWidget(name44) {
|
|
11382
11475
|
return this.widgets.get(name44);
|
|
11383
11476
|
}
|
|
@@ -11406,8 +11499,9 @@ var init_ui_shared = __esm({
|
|
|
11406
11499
|
const output = options["output"] ?? {};
|
|
11407
11500
|
const uiConfig = options["uiConfig"];
|
|
11408
11501
|
const resourceMode = uiConfig?.["resourceMode"] ?? options["resourceMode"];
|
|
11409
|
-
if (!toolName
|
|
11502
|
+
if (!toolName) return;
|
|
11410
11503
|
this.updateResourceMetaFromConfig(toolName, uiConfig);
|
|
11504
|
+
if (!template) return;
|
|
11411
11505
|
const result = renderToolTemplate({
|
|
11412
11506
|
toolName,
|
|
11413
11507
|
input,
|
|
@@ -11416,6 +11510,7 @@ var init_ui_shared = __esm({
|
|
|
11416
11510
|
resolver: this.resolver,
|
|
11417
11511
|
resourceMode,
|
|
11418
11512
|
sizing: extractSizing(uiConfig),
|
|
11513
|
+
csp: uiConfig?.["csp"],
|
|
11419
11514
|
...this.stringResultOptions(uiConfig)
|
|
11420
11515
|
});
|
|
11421
11516
|
this.widgets.set(toolName, result.html);
|
|
@@ -11494,6 +11589,7 @@ var init_ui_shared = __esm({
|
|
|
11494
11589
|
resolver: this.resolver,
|
|
11495
11590
|
resourceMode,
|
|
11496
11591
|
sizing: extractSizing(uiConfig),
|
|
11592
|
+
csp: uiConfig?.["csp"],
|
|
11497
11593
|
...this.stringResultOptions(uiConfig)
|
|
11498
11594
|
});
|
|
11499
11595
|
return { meta: result.meta };
|
|
@@ -11546,26 +11642,14 @@ function handleUIResourceRead(uri, registry, platformType) {
|
|
|
11546
11642
|
return { handled: false };
|
|
11547
11643
|
}
|
|
11548
11644
|
const mimeType = getUIResourceMimeType(platformType);
|
|
11549
|
-
const
|
|
11645
|
+
const customTool = registry.resolveCustomUri(uri);
|
|
11646
|
+
const widgetParsed = customTool ? { toolName: customTool } : parseWidgetUri(uri);
|
|
11550
11647
|
if (widgetParsed) {
|
|
11551
|
-
|
|
11552
|
-
|
|
11553
|
-
if (cachedWidget) {
|
|
11554
|
-
return {
|
|
11555
|
-
handled: true,
|
|
11556
|
-
result: {
|
|
11557
|
-
contents: [
|
|
11558
|
-
{
|
|
11559
|
-
uri,
|
|
11560
|
-
mimeType,
|
|
11561
|
-
text: cachedWidget,
|
|
11562
|
-
...resourceMeta ? { _meta: resourceMeta } : {}
|
|
11563
|
-
}
|
|
11564
|
-
]
|
|
11565
|
-
}
|
|
11566
|
-
};
|
|
11648
|
+
if (!registry.hasTool(widgetParsed.toolName)) {
|
|
11649
|
+
return { handled: true, error: `No UI widget is registered for ${uri}` };
|
|
11567
11650
|
}
|
|
11568
|
-
const
|
|
11651
|
+
const resourceMeta = buildResourceMetaForWidget(registry, widgetParsed.toolName);
|
|
11652
|
+
const html = registry.getStaticWidget(widgetParsed.toolName) ?? generatePlaceholderWidget(widgetParsed.toolName);
|
|
11569
11653
|
return {
|
|
11570
11654
|
handled: true,
|
|
11571
11655
|
result: {
|
|
@@ -12299,19 +12383,34 @@ var init_direct_client = __esm({
|
|
|
12299
12383
|
// Resource Operations (raw format)
|
|
12300
12384
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
12301
12385
|
async listResources() {
|
|
12302
|
-
|
|
12386
|
+
const resources = await listAllPages("resources/list", async (cursor) => {
|
|
12387
|
+
const result = await this.mcpClient.listResources(cursor ? { cursor } : void 0);
|
|
12388
|
+
return { items: result.resources, nextCursor: result.nextCursor };
|
|
12389
|
+
});
|
|
12390
|
+
return { resources };
|
|
12303
12391
|
}
|
|
12304
12392
|
async readResource(uri) {
|
|
12305
12393
|
return this.mcpClient.readResource({ uri });
|
|
12306
12394
|
}
|
|
12307
12395
|
async listResourceTemplates() {
|
|
12308
|
-
|
|
12396
|
+
const resourceTemplates = await listAllPages(
|
|
12397
|
+
"resources/templates/list",
|
|
12398
|
+
async (cursor) => {
|
|
12399
|
+
const result = await this.mcpClient.listResourceTemplates(cursor ? { cursor } : void 0);
|
|
12400
|
+
return { items: result.resourceTemplates, nextCursor: result.nextCursor };
|
|
12401
|
+
}
|
|
12402
|
+
);
|
|
12403
|
+
return { resourceTemplates };
|
|
12309
12404
|
}
|
|
12310
12405
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
12311
12406
|
// Prompt Operations (raw format)
|
|
12312
12407
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
12313
12408
|
async listPrompts() {
|
|
12314
|
-
|
|
12409
|
+
const prompts = await listAllPages("prompts/list", async (cursor) => {
|
|
12410
|
+
const result = await this.mcpClient.listPrompts(cursor ? { cursor } : void 0);
|
|
12411
|
+
return { items: result.prompts, nextCursor: result.nextCursor };
|
|
12412
|
+
});
|
|
12413
|
+
return { prompts };
|
|
12315
12414
|
}
|
|
12316
12415
|
async getPrompt(name44, args) {
|
|
12317
12416
|
return this.mcpClient.getPrompt({
|
|
@@ -13039,8 +13138,8 @@ function createRemoteAppProbe(appId, provider) {
|
|
|
13039
13138
|
name: `remote:${appId}`,
|
|
13040
13139
|
async check() {
|
|
13041
13140
|
const result = provider.getHealthStatus(appId);
|
|
13042
|
-
if (!result) {
|
|
13043
|
-
return { status: "
|
|
13141
|
+
if (!result || result.status === "unknown" && result.consecutiveFailures === 0) {
|
|
13142
|
+
return { status: "degraded", details: { state: "unknown" } };
|
|
13044
13143
|
}
|
|
13045
13144
|
return {
|
|
13046
13145
|
status: result.status === "healthy" ? "healthy" : result.status === "degraded" ? "degraded" : "unhealthy",
|
|
@@ -14153,6 +14252,12 @@ var init_frontmcp_context = __esm({
|
|
|
14153
14252
|
// =====================
|
|
14154
14253
|
/** Request metadata (headers, user-agent, etc.) */
|
|
14155
14254
|
metadata;
|
|
14255
|
+
/**
|
|
14256
|
+
* The hosting platform's bindings object for this request: a Cloudflare Worker's `env`
|
|
14257
|
+
* (KV namespaces, D1 databases, R2 buckets, Durable Objects, `[vars]`, secrets).
|
|
14258
|
+
* `undefined` on Node, where bindings do not exist.
|
|
14259
|
+
*/
|
|
14260
|
+
platformEnv;
|
|
14156
14261
|
// =====================
|
|
14157
14262
|
// Auth (mutable, progressively populated)
|
|
14158
14263
|
// =====================
|
|
@@ -14188,6 +14293,7 @@ var init_frontmcp_context = __esm({
|
|
|
14188
14293
|
this.traceContext = args.traceContext ?? generateTraceContext();
|
|
14189
14294
|
this.timestamp = args.timestamp ?? Date.now();
|
|
14190
14295
|
this._authInfo = args.authInfo ?? {};
|
|
14296
|
+
this.platformEnv = args.platformEnv;
|
|
14191
14297
|
this.config = {
|
|
14192
14298
|
forwardCallerTokenTo: toOrigins(args.config?.forwardCallerTokenTo),
|
|
14193
14299
|
forwardCustomHeadersTo: toOrigins(args.config?.forwardCustomHeadersTo),
|
|
@@ -14889,6 +14995,7 @@ var init_frontmcp_context_storage = __esm({
|
|
|
14889
14995
|
"use strict";
|
|
14890
14996
|
init_decorators();
|
|
14891
14997
|
init_metadata();
|
|
14998
|
+
init_tokens();
|
|
14892
14999
|
init_mcp_error();
|
|
14893
15000
|
init_frontmcp_context();
|
|
14894
15001
|
init_metadata_utils();
|
|
@@ -14956,7 +15063,12 @@ var init_frontmcp_context_storage = __esm({
|
|
|
14956
15063
|
const headers = request.headers ?? {};
|
|
14957
15064
|
const headerSessionId = typeof headers["mcp-session-id"] === "string" ? headers["mcp-session-id"].trim() : "";
|
|
14958
15065
|
const sessionId = headerSessionId.length > 0 ? headerSessionId : `anon:${randomUUID9()}`;
|
|
14959
|
-
|
|
15066
|
+
const platformEnv = request[ServerRequestTokens.webEnv];
|
|
15067
|
+
return this.runFromHeaders(
|
|
15068
|
+
headers,
|
|
15069
|
+
{ sessionId, scopeId, peerAddress: request.socket?.remoteAddress, platformEnv },
|
|
15070
|
+
fn
|
|
15071
|
+
);
|
|
14960
15072
|
}
|
|
14961
15073
|
withServerConfig(args) {
|
|
14962
15074
|
return { ...args, config: { ...this.contextConfig, ...args.config } };
|
|
@@ -22018,6 +22130,14 @@ import {
|
|
|
22018
22130
|
isDevelopment as isDevelopment2,
|
|
22019
22131
|
randomUUID as randomUUID11
|
|
22020
22132
|
} from "@frontmcp/utils";
|
|
22133
|
+
function declaredOutput(tool, output) {
|
|
22134
|
+
if (!tool?.outputSchema || typeof output !== "object" || output === null || Array.isArray(output)) return output;
|
|
22135
|
+
if (Array.isArray(output["content"])) return output;
|
|
22136
|
+
const parsed = tool.safeParseOutput(output);
|
|
22137
|
+
if (!parsed.success) return output;
|
|
22138
|
+
const structured = parsed.data.structuredContent;
|
|
22139
|
+
return typeof structured === "object" && structured !== null && !Array.isArray(structured) ? structured : output;
|
|
22140
|
+
}
|
|
22021
22141
|
function toolNameCandidates(name44) {
|
|
22022
22142
|
if (!/[-_]/.test(name44)) return [name44];
|
|
22023
22143
|
return [name44, name44.includes("_") ? name44.replace(/_/g, "-") : name44.replace(/-/g, "_")];
|
|
@@ -22745,7 +22865,7 @@ ${JSON.stringify(error.schema, null, 2)}
|
|
|
22745
22865
|
this.logger.verbose("execute:done (elicitation fire-and-forget fallback)");
|
|
22746
22866
|
return;
|
|
22747
22867
|
}
|
|
22748
|
-
if (isClientFacingError(error)) throw error;
|
|
22868
|
+
if (isClientFacingError(error) || error instanceof InvalidOutputError) throw error;
|
|
22749
22869
|
throw new ToolExecutionError(
|
|
22750
22870
|
this.state.tool?.metadata.name || "unknown",
|
|
22751
22871
|
error instanceof Error ? error : void 0
|
|
@@ -22788,7 +22908,8 @@ ${JSON.stringify(error.schema, null, 2)}
|
|
|
22788
22908
|
}
|
|
22789
22909
|
async applyUI() {
|
|
22790
22910
|
this.logger.verbose("applyUI:start");
|
|
22791
|
-
const { tool, rawOutput, input } = this.state;
|
|
22911
|
+
const { tool, rawOutput: executeOutput, input } = this.state;
|
|
22912
|
+
const rawOutput = declaredOutput(tool, executeOutput);
|
|
22792
22913
|
const ctx = this.input.ctx;
|
|
22793
22914
|
if (ctx?._skipUI) {
|
|
22794
22915
|
this.logger.verbose("applyUI:skip (agent context - structured data only)");
|
|
@@ -24413,6 +24534,38 @@ var init_context = __esm({
|
|
|
24413
24534
|
}
|
|
24414
24535
|
});
|
|
24415
24536
|
|
|
24537
|
+
// libs/sdk/src/errors/misconfiguration.ts
|
|
24538
|
+
function findMisconfiguration(error) {
|
|
24539
|
+
let current = error;
|
|
24540
|
+
for (let depth = 0; current instanceof Error && depth < 5; depth++) {
|
|
24541
|
+
const code = current.code;
|
|
24542
|
+
if (typeof code === "string" && Object.prototype.hasOwnProperty.call(MISCONFIGURATION_REMEDIES, code)) {
|
|
24543
|
+
return { code, remedy: MISCONFIGURATION_REMEDIES[code] };
|
|
24544
|
+
}
|
|
24545
|
+
if (current.name === "ZodError") return { code: "CONFIG_INVALID", remedy: CONFIG_INVALID_REMEDY };
|
|
24546
|
+
current = current.cause;
|
|
24547
|
+
}
|
|
24548
|
+
return void 0;
|
|
24549
|
+
}
|
|
24550
|
+
function misconfigurationBody(m) {
|
|
24551
|
+
return { error: "server_misconfigured", code: m.code, message: m.remedy };
|
|
24552
|
+
}
|
|
24553
|
+
var MISCONFIGURATION_REMEDIES, CONFIG_INVALID_REMEDY;
|
|
24554
|
+
var init_misconfiguration = __esm({
|
|
24555
|
+
"libs/sdk/src/errors/misconfiguration.ts"() {
|
|
24556
|
+
"use strict";
|
|
24557
|
+
MISCONFIGURATION_REMEDIES = {
|
|
24558
|
+
SESSION_SECRET_REQUIRED: "Set MCP_SESSION_SECRET in the deployment environment (e.g. `wrangler secret put MCP_SESSION_SECRET` on Cloudflare, or the platform environment settings on Node, Vercel and Lambda). Session IDs are encrypted with it, and production refuses the development machine-id fallback.",
|
|
24559
|
+
JWT_SECRET_INVALID: "JWT_SECRET is present but too weak: HS256 requires at least 32 bytes (RFC 7518). Replace it with `openssl rand -hex 32`.",
|
|
24560
|
+
JWT_SECRET_REQUIRED: "Set JWT_SECRET in the deployment environment (e.g. `wrangler secret put JWT_SECRET`). Tokens are signed with it; production refuses the random per-process fallback because tokens would not survive a restart or verify across instances.",
|
|
24561
|
+
// The startup checks: a server whose entries ask for protection nothing gives them does not start.
|
|
24562
|
+
UNENFORCED_METADATA: "An entry declares a field only a plugin enforces (approval, featureFlag, ...) and no installed plugin that enforces it reaches the entry, so the server refuses to start. Install the plugin or remove the field; the server log names the entries.",
|
|
24563
|
+
AUTH_CONFIGURATION_ERROR: "The auth or authorities configuration is invalid (for example, entries declare authorities and the server has no authorities option, or a rule checks nothing), so the server refuses to start. The server log names the entries."
|
|
24564
|
+
};
|
|
24565
|
+
CONFIG_INVALID_REMEDY = "The FrontMCP configuration failed validation, so the server refuses to start. The server log names the invalid fields.";
|
|
24566
|
+
}
|
|
24567
|
+
});
|
|
24568
|
+
|
|
24416
24569
|
// libs/sdk/src/server/server.validation.ts
|
|
24417
24570
|
import { base64Decode } from "@frontmcp/utils";
|
|
24418
24571
|
function applyHeaders(res, headers) {
|
|
@@ -24724,6 +24877,7 @@ var init_flow_instance = __esm({
|
|
|
24724
24877
|
init_common();
|
|
24725
24878
|
init_context();
|
|
24726
24879
|
init_errors();
|
|
24880
|
+
init_misconfiguration();
|
|
24727
24881
|
init_server_validation();
|
|
24728
24882
|
init_flow_http_path();
|
|
24729
24883
|
init_flow_stages();
|
|
@@ -24783,6 +24937,15 @@ var init_flow_instance = __esm({
|
|
|
24783
24937
|
return writeHttpResponse(response, e.output);
|
|
24784
24938
|
}
|
|
24785
24939
|
}
|
|
24940
|
+
const misconfiguration = findMisconfiguration(e);
|
|
24941
|
+
if (misconfiguration) {
|
|
24942
|
+
this.logger.error("Server misconfigured", { flow: this.name, code: misconfiguration.code });
|
|
24943
|
+
return writeHttpResponse(response, {
|
|
24944
|
+
kind: "json",
|
|
24945
|
+
status: 500,
|
|
24946
|
+
body: misconfigurationBody(misconfiguration)
|
|
24947
|
+
});
|
|
24948
|
+
}
|
|
24786
24949
|
if (e instanceof PublicMcpError) {
|
|
24787
24950
|
const challenge = e.wwwAuthenticate;
|
|
24788
24951
|
this.logger.warn("Public error in flow", {
|
|
@@ -25494,14 +25657,39 @@ function collectDynamicProviders(klass, options) {
|
|
|
25494
25657
|
}
|
|
25495
25658
|
return out;
|
|
25496
25659
|
}
|
|
25660
|
+
function collectDynamicTools(klass, options) {
|
|
25661
|
+
const chain = [];
|
|
25662
|
+
for (let k = klass; k && k !== Function.prototype; k = Object.getPrototypeOf(k)) {
|
|
25663
|
+
chain.push(k);
|
|
25664
|
+
}
|
|
25665
|
+
const out = [];
|
|
25666
|
+
for (let i = chain.length - 1; i >= 0; i--) {
|
|
25667
|
+
const k = chain[i];
|
|
25668
|
+
if (Object.prototype.hasOwnProperty.call(k, "dynamicTools") && typeof k.dynamicTools === "function") {
|
|
25669
|
+
out.push(...k.dynamicTools(options) ?? []);
|
|
25670
|
+
}
|
|
25671
|
+
}
|
|
25672
|
+
return out;
|
|
25673
|
+
}
|
|
25674
|
+
function pluginMetadataFromOptions(options, dynamicTools) {
|
|
25675
|
+
const out = { ...options };
|
|
25676
|
+
for (const key of LIST_METADATA_KEYS) {
|
|
25677
|
+
if (key in out && !Array.isArray(out[key])) delete out[key];
|
|
25678
|
+
}
|
|
25679
|
+
const tools = [...out["tools"] ?? [], ...dynamicTools];
|
|
25680
|
+
if (tools.length > 0) out["tools"] = tools;
|
|
25681
|
+
return out;
|
|
25682
|
+
}
|
|
25497
25683
|
function dedupePluginProviders(providers) {
|
|
25498
25684
|
const map = /* @__PURE__ */ new Map();
|
|
25499
25685
|
for (const p of providers) map.set(p["provide"] ?? p, p);
|
|
25500
25686
|
return [...map.values()];
|
|
25501
25687
|
}
|
|
25688
|
+
var LIST_METADATA_KEYS;
|
|
25502
25689
|
var init_dynamic_utils = __esm({
|
|
25503
25690
|
"libs/sdk/src/common/dynamic/dynamic.utils.ts"() {
|
|
25504
25691
|
"use strict";
|
|
25692
|
+
LIST_METADATA_KEYS = ["exports", "plugins", "adapters", "tools", "resources", "prompts", "skills"];
|
|
25505
25693
|
}
|
|
25506
25694
|
});
|
|
25507
25695
|
|
|
@@ -25533,7 +25721,7 @@ var init_dynamic_plugin = __esm({
|
|
|
25533
25721
|
const typedOptions = options;
|
|
25534
25722
|
if ("useFactory" in options) {
|
|
25535
25723
|
return {
|
|
25536
|
-
...typedOptions,
|
|
25724
|
+
...pluginMetadataFromOptions(typedOptions, []),
|
|
25537
25725
|
provide: this,
|
|
25538
25726
|
inject: options.inject,
|
|
25539
25727
|
useFactory: options.useFactory,
|
|
@@ -25542,10 +25730,11 @@ var init_dynamic_plugin = __esm({
|
|
|
25542
25730
|
}
|
|
25543
25731
|
const dyn = collectDynamicProviders(this, typedOptions);
|
|
25544
25732
|
const mergedProviders = dedupePluginProviders([...dyn ?? [], ...extraProviders ?? []]);
|
|
25733
|
+
const dynamicTools = collectDynamicTools(this, typedOptions);
|
|
25545
25734
|
const instance = new this(options);
|
|
25546
25735
|
initOptionsByInstance.set(instance, options);
|
|
25547
25736
|
return {
|
|
25548
|
-
...typedOptions,
|
|
25737
|
+
...pluginMetadataFromOptions(typedOptions, dynamicTools),
|
|
25549
25738
|
provide: this,
|
|
25550
25739
|
useValue: instance,
|
|
25551
25740
|
providers: mergedProviders
|
|
@@ -25807,9 +25996,18 @@ var init_execution_context_interface = __esm({
|
|
|
25807
25996
|
if (this._authContext) return this._authContext;
|
|
25808
25997
|
const rawAuth = __require("@frontmcp/auth");
|
|
25809
25998
|
const auth = rawAuth.default ?? rawAuth;
|
|
25810
|
-
this._authContext = auth.buildAuthContext(this.
|
|
25999
|
+
this._authContext = auth.buildAuthContext(this.resolveAuthSource(), this.scope.metadata.authorities?.claimsMapping);
|
|
25811
26000
|
return this._authContext;
|
|
25812
26001
|
}
|
|
26002
|
+
resolveAuthSource() {
|
|
26003
|
+
const fromContext = this.tryGetContext()?.authInfo;
|
|
26004
|
+
if (!fromContext) return this._authInfo;
|
|
26005
|
+
const merged = { ...this._authInfo };
|
|
26006
|
+
for (const [key, value] of Object.entries(fromContext)) {
|
|
26007
|
+
if (value !== void 0) merged[key] = value;
|
|
26008
|
+
}
|
|
26009
|
+
return merged;
|
|
26010
|
+
}
|
|
25813
26011
|
/**
|
|
25814
26012
|
* Try to get the context, returning undefined if not available.
|
|
25815
26013
|
*
|
|
@@ -25995,6 +26193,23 @@ var init_execution_context_interface = __esm({
|
|
|
25995
26193
|
get config() {
|
|
25996
26194
|
return this.providers.get(ConfigService);
|
|
25997
26195
|
}
|
|
26196
|
+
// ---- Platform bindings ----
|
|
26197
|
+
/**
|
|
26198
|
+
* The hosting platform's bindings for the current request — on a Cloudflare Worker, the `env`
|
|
26199
|
+
* object holding KV namespaces, D1 databases, R2 buckets, Durable Object namespaces, `[vars]`
|
|
26200
|
+
* and secrets. `undefined` on Node/Express, where no such object exists.
|
|
26201
|
+
*
|
|
26202
|
+
* Only string bindings are mirrored into `process.env`; use this for the rest.
|
|
26203
|
+
*
|
|
26204
|
+
* @example
|
|
26205
|
+
* ```typescript
|
|
26206
|
+
* const kv = this.workerEnv?.['MY_KV'] as KVNamespace | undefined;
|
|
26207
|
+
* ```
|
|
26208
|
+
*/
|
|
26209
|
+
get workerEnv() {
|
|
26210
|
+
const env = this.tryGetContext()?.platformEnv;
|
|
26211
|
+
return env !== null && typeof env === "object" ? env : void 0;
|
|
26212
|
+
}
|
|
25998
26213
|
// ---- Runtime context helpers ----
|
|
25999
26214
|
/**
|
|
26000
26215
|
* Get the current runtime context (platform, runtime, deployment, env).
|
|
@@ -30801,6 +31016,9 @@ import { AuthorityDeniedError as AuthorityDeniedError7, resolveRequiredScopes as
|
|
|
30801
31016
|
import { ExecutionTimeoutError as ExecutionTimeoutError2, withTimeout as withTimeout2 } from "@frontmcp/guard";
|
|
30802
31017
|
import { z as z60 } from "@frontmcp/lazy-zod";
|
|
30803
31018
|
import { CallToolRequestSchema as CallToolRequestSchema3, CallToolResultSchema as CallToolResultSchema3 } from "@frontmcp/protocol";
|
|
31019
|
+
function invalidOutputFrom(outputError) {
|
|
31020
|
+
return outputError instanceof InvalidOutputError ? outputError : new InvalidOutputError();
|
|
31021
|
+
}
|
|
30804
31022
|
var inputSchema15, outputSchema14, stateSchema14, plan14, name14, Stage14, CallAgentFlow;
|
|
30805
31023
|
var init_call_agent_flow = __esm({
|
|
30806
31024
|
"libs/sdk/src/agent/flows/call-agent.flow.ts"() {
|
|
@@ -31183,7 +31401,7 @@ var init_call_agent_flow = __esm({
|
|
|
31183
31401
|
const { agent, agentContext, executionStartedAt, executionError, outputError, authInfo } = this.state;
|
|
31184
31402
|
if (!agent || executionStartedAt === void 0) return;
|
|
31185
31403
|
if (executionError instanceof ElicitationFallbackRequired || executionError instanceof FlowControl) return;
|
|
31186
|
-
const failure = executionError ?? (outputError !== void 0 ?
|
|
31404
|
+
const failure = executionError ?? (outputError !== void 0 ? invalidOutputFrom(outputError) : void 0);
|
|
31187
31405
|
const failed = failure !== void 0;
|
|
31188
31406
|
const requestId = this.tryGetContext()?.requestId;
|
|
31189
31407
|
const sessionId = typeof authInfo?.sessionId === "string" && authInfo.sessionId ? authInfo.sessionId : void 0;
|
|
@@ -31213,7 +31431,7 @@ var init_call_agent_flow = __esm({
|
|
|
31213
31431
|
agent: agent.metadata.name,
|
|
31214
31432
|
errors: outputError
|
|
31215
31433
|
});
|
|
31216
|
-
throw
|
|
31434
|
+
throw invalidOutputFrom(outputError);
|
|
31217
31435
|
}
|
|
31218
31436
|
const result = parsedOutput;
|
|
31219
31437
|
if (executionMeta) {
|
|
@@ -32850,6 +33068,10 @@ var init_mcp_stateless_client = __esm({
|
|
|
32850
33068
|
async listResources(cursor) {
|
|
32851
33069
|
return this.request("resources/list", cursor ? { cursor } : {});
|
|
32852
33070
|
}
|
|
33071
|
+
/** One page of `resources/templates/list`; pass the previous page's `nextCursor` for the next. */
|
|
33072
|
+
async listResourceTemplates(cursor) {
|
|
33073
|
+
return this.request("resources/templates/list", cursor ? { cursor } : {});
|
|
33074
|
+
}
|
|
32853
33075
|
/** One page of `prompts/list`; pass the previous page's `nextCursor` for the next. */
|
|
32854
33076
|
async listPrompts(cursor) {
|
|
32855
33077
|
return this.request("prompts/list", cursor ? { cursor } : {});
|
|
@@ -33191,6 +33413,13 @@ var init_mcp_stateless_client_adapter = __esm({
|
|
|
33191
33413
|
...nextCursorOf(result)
|
|
33192
33414
|
};
|
|
33193
33415
|
}
|
|
33416
|
+
async listResourceTemplates(params) {
|
|
33417
|
+
const result = await this.client.listResourceTemplates(params?.cursor);
|
|
33418
|
+
return {
|
|
33419
|
+
resourceTemplates: Array.isArray(result["resourceTemplates"]) ? result["resourceTemplates"] : [],
|
|
33420
|
+
...nextCursorOf(result)
|
|
33421
|
+
};
|
|
33422
|
+
}
|
|
33194
33423
|
async readResource(params) {
|
|
33195
33424
|
return this.client.readResource(params.uri);
|
|
33196
33425
|
}
|
|
@@ -33558,7 +33787,7 @@ var init_health_check = __esm({
|
|
|
33558
33787
|
this.consecutiveSuccesses++;
|
|
33559
33788
|
this.consecutiveFailures = 0;
|
|
33560
33789
|
const previousStatus = this.status;
|
|
33561
|
-
if (this.consecutiveSuccesses >= this.options.healthyThreshold) {
|
|
33790
|
+
if (previousStatus === "unknown" || this.consecutiveSuccesses >= this.options.healthyThreshold) {
|
|
33562
33791
|
this.status = latencyMs > this.options.degradedLatencyMs ? "degraded" : "healthy";
|
|
33563
33792
|
}
|
|
33564
33793
|
this.lastResult = {
|
|
@@ -42904,9 +43133,11 @@ var init_instance_local_primary_auth = __esm({
|
|
|
42904
43133
|
} else if (isProduction6() && isOrchestratedMode(options)) {
|
|
42905
43134
|
throw new JwtSecretRequiredError(options.mode);
|
|
42906
43135
|
} else {
|
|
42907
|
-
|
|
42908
|
-
|
|
42909
|
-
|
|
43136
|
+
if (!isPublicMode(options)) {
|
|
43137
|
+
this.logger.warn(
|
|
43138
|
+
"JWT_SECRET is not set; signing with a random per-process secret. Tokens will not survive a restart and will not verify across instances. Set JWT_SECRET for any deployment that mints tokens."
|
|
43139
|
+
);
|
|
43140
|
+
}
|
|
42910
43141
|
this.secret = getDefaultNoAuthSecret();
|
|
42911
43142
|
}
|
|
42912
43143
|
this.tokenStorage = this.readTokenStorage(options);
|
|
@@ -46883,7 +47114,14 @@ var init_encrypted_elicitation_store = __esm({
|
|
|
46883
47114
|
|
|
46884
47115
|
// libs/sdk/src/elicitation/store/elicitation-store.factory.ts
|
|
46885
47116
|
import { createMemoryStorage as createMemoryStorage2, createStorage as createStorage2, getEnv as getEnv7 } from "@frontmcp/utils";
|
|
46886
|
-
function detectStorageType2(storage) {
|
|
47117
|
+
function detectStorageType2(storage, configuredType) {
|
|
47118
|
+
if (configuredType === "redis" || configuredType === "upstash" || configuredType === "memory") {
|
|
47119
|
+
return configuredType;
|
|
47120
|
+
}
|
|
47121
|
+
const backend = storage.type ?? storage.backendName;
|
|
47122
|
+
if (backend === "redis" || backend === "upstash" || backend === "memory") {
|
|
47123
|
+
return backend;
|
|
47124
|
+
}
|
|
46887
47125
|
if (getEnv7("UPSTASH_REDIS_REST_URL")) {
|
|
46888
47126
|
return "upstash";
|
|
46889
47127
|
}
|
|
@@ -46973,7 +47211,8 @@ async function createElicitationStore(options = {}) {
|
|
|
46973
47211
|
});
|
|
46974
47212
|
}
|
|
46975
47213
|
}
|
|
46976
|
-
|
|
47214
|
+
const explicitBackend = Boolean(storageConfig || redis);
|
|
47215
|
+
if (finalStorageConfig?.type === "vercel-kv" || !explicitBackend && getEnv7("KV_REST_API_URL")) {
|
|
46977
47216
|
throw new ElicitationNotSupportedError(
|
|
46978
47217
|
"Vercel KV is not supported for elicitation stores. Elicitation requires pub/sub for cross-node result routing, which Vercel KV does not support. Use Redis or Upstash instead."
|
|
46979
47218
|
);
|
|
@@ -46994,7 +47233,7 @@ async function createElicitationStore(options = {}) {
|
|
|
46994
47233
|
);
|
|
46995
47234
|
}
|
|
46996
47235
|
let store = new StorageElicitationStore(storage, logger);
|
|
46997
|
-
const type = detectStorageType2(storage);
|
|
47236
|
+
const type = detectStorageType2(storage, finalStorageConfig?.type);
|
|
46998
47237
|
const encryptionEnabled = encryption?.enabled ?? "auto";
|
|
46999
47238
|
const encryptionSecret = encryption?.secret;
|
|
47000
47239
|
const hasSecret = encryptionSecret || isElicitationEncryptionAvailable();
|
|
@@ -49806,6 +50045,7 @@ var init_redis_job_definition_store = __esm({
|
|
|
49806
50045
|
});
|
|
49807
50046
|
|
|
49808
50047
|
// libs/sdk/src/job/store/job-definition-store.factory.ts
|
|
50048
|
+
import { attachRedisErrorListener } from "@frontmcp/utils";
|
|
49809
50049
|
function createJobDefinitionStore(options, logger) {
|
|
49810
50050
|
const keyPrefix = options?.keyPrefix ?? "mcp:jobs:def:";
|
|
49811
50051
|
const effectiveLogger = logger ?? noopLogger2;
|
|
@@ -49817,6 +50057,7 @@ function createJobDefinitionStore(options, logger) {
|
|
|
49817
50057
|
host: options.redis.host ?? "localhost",
|
|
49818
50058
|
port: options.redis.port ?? 6379
|
|
49819
50059
|
});
|
|
50060
|
+
attachRedisErrorListener(client, { label: "JobDefinitionStore", logger: effectiveLogger });
|
|
49820
50061
|
return {
|
|
49821
50062
|
// Factory-created client lifetime is bound to the store, so the store
|
|
49822
50063
|
// must close it on dispose() — pass ownsClient: true.
|
|
@@ -50017,6 +50258,7 @@ var init_redis_job_state_store = __esm({
|
|
|
50017
50258
|
});
|
|
50018
50259
|
|
|
50019
50260
|
// libs/sdk/src/job/store/job-state-store.factory.ts
|
|
50261
|
+
import { attachRedisErrorListener as attachRedisErrorListener2 } from "@frontmcp/utils";
|
|
50020
50262
|
function createJobStateStore(options, logger) {
|
|
50021
50263
|
const keyPrefix = options?.keyPrefix ?? "mcp:jobs:";
|
|
50022
50264
|
const ttlSeconds = options?.ttlSeconds ?? 86400;
|
|
@@ -50028,6 +50270,7 @@ function createJobStateStore(options, logger) {
|
|
|
50028
50270
|
host: options.redis.host ?? "localhost",
|
|
50029
50271
|
port: options.redis.port ?? 6379
|
|
50030
50272
|
});
|
|
50273
|
+
attachRedisErrorListener2(client, { label: "JobStateStore", logger });
|
|
50031
50274
|
return {
|
|
50032
50275
|
store: new RedisJobStateStore2(client, logger, keyPrefix, ttlSeconds),
|
|
50033
50276
|
type: "redis"
|
|
@@ -52443,6 +52686,7 @@ var init_skill_http_cache = __esm({
|
|
|
52443
52686
|
});
|
|
52444
52687
|
|
|
52445
52688
|
// libs/sdk/src/skill/cache/skill-http-cache.factory.ts
|
|
52689
|
+
import { attachRedisErrorListener as attachRedisErrorListener3 } from "@frontmcp/utils";
|
|
52446
52690
|
function hasRedisProvider(redis) {
|
|
52447
52691
|
if (!redis?.provider) return false;
|
|
52448
52692
|
const provider = redis.provider;
|
|
@@ -52487,6 +52731,7 @@ async function createRedisCache(redis, keyPrefix, ttlMs, logger) {
|
|
|
52487
52731
|
enableReadyCheck: false,
|
|
52488
52732
|
maxRetriesPerRequest: 3
|
|
52489
52733
|
});
|
|
52734
|
+
attachRedisErrorListener3(client, { label: "SkillHttpCache" });
|
|
52490
52735
|
await client.connect();
|
|
52491
52736
|
return new RedisSkillHttpCache({
|
|
52492
52737
|
getClient: async () => client,
|
|
@@ -56302,6 +56547,51 @@ var init_skill_scope_helper = __esm({
|
|
|
56302
56547
|
}
|
|
56303
56548
|
});
|
|
56304
56549
|
|
|
56550
|
+
// libs/sdk/src/tool/ui/ui-option-warnings.ts
|
|
56551
|
+
function describeIgnoredUiOptions(toolName, ui) {
|
|
56552
|
+
if (!ui) return [];
|
|
56553
|
+
const messages = [];
|
|
56554
|
+
const ignored = IGNORED_UI_OPTIONS.filter((key) => ui[key] !== void 0);
|
|
56555
|
+
if (ignored.length > 0) {
|
|
56556
|
+
messages.push(
|
|
56557
|
+
`Tool "${toolName}": \`ui.${ignored.join("`, `ui.")}\` ${ignored.length === 1 ? "is" : "are"} accepted but not used yet, so ${ignored.length === 1 ? "it has" : "they have"} no effect.`
|
|
56558
|
+
);
|
|
56559
|
+
}
|
|
56560
|
+
const mode = ui["servingMode"];
|
|
56561
|
+
if (UNSUPPORTED_SERVING_MODES.includes(mode)) {
|
|
56562
|
+
messages.push(
|
|
56563
|
+
`Tool "${toolName}": \`ui.servingMode: '${String(mode)}'\` is not implemented; the widget is served inline, as with \`servingMode: 'inline'\`.`
|
|
56564
|
+
);
|
|
56565
|
+
}
|
|
56566
|
+
if (mode === "hybrid") {
|
|
56567
|
+
messages.push(
|
|
56568
|
+
`Tool "${toolName}": \`ui.servingMode: 'hybrid'\` sends only a reference in \`_meta['ui/component']\` ({ type, hash, toolName }), not the component code; the widget is rendered from its \`ui://\` resource.`
|
|
56569
|
+
);
|
|
56570
|
+
}
|
|
56571
|
+
return messages;
|
|
56572
|
+
}
|
|
56573
|
+
var IGNORED_UI_OPTIONS, UNSUPPORTED_SERVING_MODES;
|
|
56574
|
+
var init_ui_option_warnings = __esm({
|
|
56575
|
+
"libs/sdk/src/tool/ui/ui-option-warnings.ts"() {
|
|
56576
|
+
"use strict";
|
|
56577
|
+
IGNORED_UI_OPTIONS = [
|
|
56578
|
+
"widgetDescription",
|
|
56579
|
+
"widgetAccessible",
|
|
56580
|
+
"displayMode",
|
|
56581
|
+
"prefersBorder",
|
|
56582
|
+
"sandboxDomain",
|
|
56583
|
+
"contentSecurity",
|
|
56584
|
+
"hydrate",
|
|
56585
|
+
"runtimeOptions",
|
|
56586
|
+
"mdxComponents",
|
|
56587
|
+
"bundlingMode",
|
|
56588
|
+
"uiType",
|
|
56589
|
+
"htmlResponsePrefix"
|
|
56590
|
+
];
|
|
56591
|
+
UNSUPPORTED_SERVING_MODES = ["direct-url", "custom-url"];
|
|
56592
|
+
}
|
|
56593
|
+
});
|
|
56594
|
+
|
|
56305
56595
|
// libs/sdk/src/transport/bus/redis-transport-bus.ts
|
|
56306
56596
|
var DEFAULT_BUS_PREFIX, DEFAULT_BUS_TTL_SECONDS, REVOKE_LUA, RedisTransportBus;
|
|
56307
56597
|
var init_redis_transport_bus = __esm({
|
|
@@ -56431,6 +56721,7 @@ var redis_event_store_exports = {};
|
|
|
56431
56721
|
__export(redis_event_store_exports, {
|
|
56432
56722
|
RedisEventStore: () => RedisEventStore
|
|
56433
56723
|
});
|
|
56724
|
+
import { attachRedisErrorListener as attachRedisErrorListener4 } from "@frontmcp/utils";
|
|
56434
56725
|
var RedisEventStore;
|
|
56435
56726
|
var init_redis_event_store = __esm({
|
|
56436
56727
|
"libs/sdk/src/transport/event-stores/redis.event-store.ts"() {
|
|
@@ -56478,6 +56769,9 @@ var init_redis_event_store = __esm({
|
|
|
56478
56769
|
enableReadyCheck: false,
|
|
56479
56770
|
maxRetriesPerRequest: 3
|
|
56480
56771
|
});
|
|
56772
|
+
attachRedisErrorListener4(this.client, {
|
|
56773
|
+
label: "RedisEventStore"
|
|
56774
|
+
});
|
|
56481
56775
|
await this.client.connect();
|
|
56482
56776
|
}
|
|
56483
56777
|
return this.client;
|
|
@@ -57130,7 +57424,7 @@ var init_handle_mcp_20260728_flow = __esm({
|
|
|
57130
57424
|
const sink = new RequestNotificationSink(logLevel, progressToken);
|
|
57131
57425
|
const clientInfo = toClientInfo(meta[MCP_20260728_META5.clientInfo]);
|
|
57132
57426
|
if (clientInfo) {
|
|
57133
|
-
const platformType = detectAIPlatform(clientInfo, this.scope.metadata.transport?.platformDetection);
|
|
57427
|
+
const platformType = detectPlatformFromCapabilities(clientCapabilities) ?? detectAIPlatform(clientInfo, this.scope.metadata.transport?.platformDetection);
|
|
57134
57428
|
this.tryGetContext()?.setClientInfo(clientInfo, platformType);
|
|
57135
57429
|
}
|
|
57136
57430
|
const dispatchOptions = {
|
|
@@ -57228,6 +57522,28 @@ var init_handle_mcp_20260728_flow = __esm({
|
|
|
57228
57522
|
}
|
|
57229
57523
|
});
|
|
57230
57524
|
|
|
57525
|
+
// libs/sdk/src/ha/ha-headers.ts
|
|
57526
|
+
import { buildSetCookie, getMachineId as getMachineId3, getRuntimeContext as getRuntimeContext13 } from "@frontmcp/utils";
|
|
57527
|
+
function applyMachineIdHeader(response) {
|
|
57528
|
+
if (getRuntimeContext13().deployment !== "distributed") return false;
|
|
57529
|
+
response.setHeader(DEFAULT_FRONTMCP_MACHINE_ID_HEADER, getMachineId3());
|
|
57530
|
+
return true;
|
|
57531
|
+
}
|
|
57532
|
+
function applyNodeAffinity(response, request) {
|
|
57533
|
+
if (!applyMachineIdHeader(response)) return;
|
|
57534
|
+
const cookie = buildSetCookie({ name: DEFAULT_FRONTMCP_NODE_COOKIE, value: getMachineId3() }, request);
|
|
57535
|
+
if (!cookie) return;
|
|
57536
|
+
const existing = response.getHeader("Set-Cookie");
|
|
57537
|
+
const existingArr = Array.isArray(existing) ? existing : existing ? [String(existing)] : [];
|
|
57538
|
+
response.setHeader("Set-Cookie", [...existingArr, cookie]);
|
|
57539
|
+
}
|
|
57540
|
+
var init_ha_headers = __esm({
|
|
57541
|
+
"libs/sdk/src/ha/ha-headers.ts"() {
|
|
57542
|
+
"use strict";
|
|
57543
|
+
init_ha_constants();
|
|
57544
|
+
}
|
|
57545
|
+
});
|
|
57546
|
+
|
|
57231
57547
|
// libs/sdk/src/skill/skill-mode.utils.ts
|
|
57232
57548
|
function detectSkillsOnlyMode(query) {
|
|
57233
57549
|
if (!query) return false;
|
|
@@ -57248,7 +57564,7 @@ var init_skill_mode_utils = __esm({
|
|
|
57248
57564
|
|
|
57249
57565
|
// libs/sdk/src/transport/flows/handle.sse.flow.ts
|
|
57250
57566
|
import { z as z103 } from "@frontmcp/lazy-zod";
|
|
57251
|
-
import {
|
|
57567
|
+
import { getMachineId as getMachineId4, getRuntimeContext as getRuntimeContext14 } from "@frontmcp/utils";
|
|
57252
57568
|
function getQuerySessionId2(urlPath) {
|
|
57253
57569
|
if (!urlPath) return void 0;
|
|
57254
57570
|
try {
|
|
@@ -57289,7 +57605,7 @@ var init_handle_sse_flow = __esm({
|
|
|
57289
57605
|
init_session_id_utils();
|
|
57290
57606
|
init_common();
|
|
57291
57607
|
init_errors();
|
|
57292
|
-
|
|
57608
|
+
init_ha_headers();
|
|
57293
57609
|
init_skill_mode_utils();
|
|
57294
57610
|
plan38 = {
|
|
57295
57611
|
pre: ["parseInput", "router"],
|
|
@@ -57363,16 +57679,7 @@ var init_handle_sse_flow = __esm({
|
|
|
57363
57679
|
const { request, response } = this.rawInput;
|
|
57364
57680
|
const { token, session } = this.state.required;
|
|
57365
57681
|
const transport = await transportService.createTransporter("sse", token, session.id, response);
|
|
57366
|
-
|
|
57367
|
-
const nodeId = getMachineId3();
|
|
57368
|
-
response.setHeader(DEFAULT_FRONTMCP_MACHINE_ID_HEADER, nodeId);
|
|
57369
|
-
const cookie = buildSetCookie({ name: DEFAULT_FRONTMCP_NODE_COOKIE, value: nodeId }, request);
|
|
57370
|
-
if (cookie) {
|
|
57371
|
-
const existing = response.getHeader("Set-Cookie");
|
|
57372
|
-
const existingArr = Array.isArray(existing) ? existing : existing ? [String(existing)] : [];
|
|
57373
|
-
response.setHeader("Set-Cookie", [...existingArr, cookie]);
|
|
57374
|
-
}
|
|
57375
|
-
}
|
|
57682
|
+
applyNodeAffinity(response, request);
|
|
57376
57683
|
await transport.initialize(request, response);
|
|
57377
57684
|
this.handled();
|
|
57378
57685
|
}
|
|
@@ -57388,12 +57695,12 @@ var init_handle_sse_flow = __esm({
|
|
|
57388
57695
|
const { request, response } = this.rawInput;
|
|
57389
57696
|
const { token, session } = this.state.required;
|
|
57390
57697
|
const transport = await transportService.getTransporter("sse", token, session.id);
|
|
57391
|
-
if (!transport &&
|
|
57698
|
+
if (!transport && getRuntimeContext14().deployment === "distributed") {
|
|
57392
57699
|
const storedSession = await transportService.getStoredSession("sse", token, session.id);
|
|
57393
57700
|
if (storedSession) {
|
|
57394
57701
|
const haManager = this.scope.haManager;
|
|
57395
57702
|
const relay = haManager?.getRelay();
|
|
57396
|
-
if (relay && storedSession.session.nodeId && storedSession.session.nodeId !==
|
|
57703
|
+
if (relay && storedSession.session.nodeId && storedSession.session.nodeId !== getMachineId4()) {
|
|
57397
57704
|
const isAlive2 = await haManager.isNodeAlive(storedSession.session.nodeId);
|
|
57398
57705
|
if (isAlive2) {
|
|
57399
57706
|
try {
|
|
@@ -57488,9 +57795,10 @@ var init_handle_stateless_http_flow = __esm({
|
|
|
57488
57795
|
"use strict";
|
|
57489
57796
|
init_common();
|
|
57490
57797
|
init_errors();
|
|
57798
|
+
init_ha_headers();
|
|
57491
57799
|
init_transport_types();
|
|
57492
57800
|
plan39 = {
|
|
57493
|
-
pre: ["parseInput", "router"],
|
|
57801
|
+
pre: ["applyNodeHeaders", "parseInput", "router"],
|
|
57494
57802
|
execute: ["handleRequest"],
|
|
57495
57803
|
post: [],
|
|
57496
57804
|
finalize: ["cleanup"]
|
|
@@ -57504,6 +57812,9 @@ var init_handle_stateless_http_flow = __esm({
|
|
|
57504
57812
|
({ Stage: Stage40 } = FlowHooksOf(name40));
|
|
57505
57813
|
HandleStatelessHttpFlow = class extends FlowBase {
|
|
57506
57814
|
name = name40;
|
|
57815
|
+
async applyNodeHeaders() {
|
|
57816
|
+
applyMachineIdHeader(this.rawInput.response);
|
|
57817
|
+
}
|
|
57507
57818
|
async parseInput() {
|
|
57508
57819
|
const { request } = this.rawInput;
|
|
57509
57820
|
const logger = this.scope.logger.child("HandleStatelessHttpFlow");
|
|
@@ -57565,6 +57876,9 @@ var init_handle_stateless_http_flow = __esm({
|
|
|
57565
57876
|
this.handled();
|
|
57566
57877
|
}
|
|
57567
57878
|
};
|
|
57879
|
+
__decorateClass([
|
|
57880
|
+
Stage40("applyNodeHeaders")
|
|
57881
|
+
], HandleStatelessHttpFlow.prototype, "applyNodeHeaders", 1);
|
|
57568
57882
|
__decorateClass([
|
|
57569
57883
|
Stage40("parseInput")
|
|
57570
57884
|
], HandleStatelessHttpFlow.prototype, "parseInput", 1);
|
|
@@ -57944,7 +58258,6 @@ var init_ext_apps2 = __esm({
|
|
|
57944
58258
|
// libs/sdk/src/transport/flows/handle.streamable-http.flow.ts
|
|
57945
58259
|
import { z as z105 } from "@frontmcp/lazy-zod";
|
|
57946
58260
|
import { CallToolResultSchema as CallToolResultSchema4, ElicitResultSchema, RequestSchema as RequestSchema2 } from "@frontmcp/protocol";
|
|
57947
|
-
import { buildSetCookie as buildSetCookie2, getMachineId as getMachineId4, getRuntimeContext as getRuntimeContext14 } from "@frontmcp/utils";
|
|
57948
58261
|
function resolveStreamableHttpSession(params) {
|
|
57949
58262
|
const { rawHeader, authorizationSession, createSession } = params;
|
|
57950
58263
|
const rawMcpSessionHeader = typeof rawHeader === "string" ? rawHeader : void 0;
|
|
@@ -58040,11 +58353,11 @@ var init_handle_streamable_http_flow = __esm({
|
|
|
58040
58353
|
init_common();
|
|
58041
58354
|
init_errors();
|
|
58042
58355
|
init_ext_apps2();
|
|
58043
|
-
|
|
58356
|
+
init_ha_headers();
|
|
58044
58357
|
init_skill_mode_utils();
|
|
58045
58358
|
init_mcp_surface();
|
|
58046
58359
|
plan40 = {
|
|
58047
|
-
pre: ["parseInput", "router"],
|
|
58360
|
+
pre: ["applyNodeHeaders", "parseInput", "router"],
|
|
58048
58361
|
execute: ["onInitialize", "onMessage", "onElicitResult", "onSseListener", "onExtApps"],
|
|
58049
58362
|
post: [],
|
|
58050
58363
|
finalize: ["cleanup"]
|
|
@@ -58070,6 +58383,9 @@ var init_handle_streamable_http_flow = __esm({
|
|
|
58070
58383
|
({ Stage: Stage41 } = FlowHooksOf(name41));
|
|
58071
58384
|
HandleStreamableHttpFlow = class extends FlowBase {
|
|
58072
58385
|
name = name41;
|
|
58386
|
+
async applyNodeHeaders() {
|
|
58387
|
+
applyMachineIdHeader(this.rawInput.response);
|
|
58388
|
+
}
|
|
58073
58389
|
async parseInput() {
|
|
58074
58390
|
const { request } = this.rawInput;
|
|
58075
58391
|
const authorization = request[ServerRequestTokens.auth];
|
|
@@ -58168,16 +58484,7 @@ var init_handle_streamable_http_flow = __esm({
|
|
|
58168
58484
|
return;
|
|
58169
58485
|
}
|
|
58170
58486
|
}
|
|
58171
|
-
|
|
58172
|
-
const nodeId = getMachineId4();
|
|
58173
|
-
response.setHeader(DEFAULT_FRONTMCP_MACHINE_ID_HEADER, nodeId);
|
|
58174
|
-
const cookie = buildSetCookie2({ name: DEFAULT_FRONTMCP_NODE_COOKIE, value: nodeId }, request);
|
|
58175
|
-
if (cookie) {
|
|
58176
|
-
const existing = response.getHeader("Set-Cookie");
|
|
58177
|
-
const existingArr = Array.isArray(existing) ? existing : existing ? [String(existing)] : [];
|
|
58178
|
-
response.setHeader("Set-Cookie", [...existingArr, cookie]);
|
|
58179
|
-
}
|
|
58180
|
-
}
|
|
58487
|
+
applyNodeAffinity(response, request);
|
|
58181
58488
|
logger.info("onInitialize: transport created, calling initialize");
|
|
58182
58489
|
await transport.initialize(request, response);
|
|
58183
58490
|
logger.info("onInitialize: completed successfully");
|
|
@@ -58360,6 +58667,17 @@ var init_handle_streamable_http_flow = __esm({
|
|
|
58360
58667
|
const logger = this.scopeLogger.child("handle:streamable-http:onExtApps");
|
|
58361
58668
|
const { request, response } = this.rawInput;
|
|
58362
58669
|
const { token, session } = this.state.required;
|
|
58670
|
+
if (this.scope.metadata.extApps?.enabled === false) {
|
|
58671
|
+
const rpc = request.body;
|
|
58672
|
+
logger.verbose("onExtApps: ext-apps disabled by config", { method: rpc?.method });
|
|
58673
|
+
response.status(200).json({
|
|
58674
|
+
jsonrpc: "2.0",
|
|
58675
|
+
id: rpc?.id ?? null,
|
|
58676
|
+
error: { code: -32601, message: `Method not found: ${rpc?.method}` }
|
|
58677
|
+
});
|
|
58678
|
+
this.handled();
|
|
58679
|
+
return;
|
|
58680
|
+
}
|
|
58363
58681
|
logger.info("onExtApps: starting", {
|
|
58364
58682
|
sessionId: session.id?.slice(0, 20),
|
|
58365
58683
|
method: request.body?.method
|
|
@@ -58411,8 +58729,20 @@ var init_handle_streamable_http_flow = __esm({
|
|
|
58411
58729
|
const hostCapabilities = {
|
|
58412
58730
|
serverToolProxy: configuredCapabilities?.serverToolProxy ?? true,
|
|
58413
58731
|
logging: configuredCapabilities?.logging ?? true,
|
|
58414
|
-
...configuredCapabilities
|
|
58732
|
+
...configuredCapabilities,
|
|
58733
|
+
// Only advertise what this host can serve: the context below provides callTool and nothing else
|
|
58734
|
+
openLink: false,
|
|
58735
|
+
modelContextUpdate: false,
|
|
58736
|
+
widgetTools: false
|
|
58415
58737
|
};
|
|
58738
|
+
const unsupported = ["openLink", "modelContextUpdate", "widgetTools"].filter(
|
|
58739
|
+
(key) => configuredCapabilities?.[key] === true
|
|
58740
|
+
);
|
|
58741
|
+
if (unsupported.length > 0) {
|
|
58742
|
+
logger.warn(
|
|
58743
|
+
`onExtApps: extApps.hostCapabilities ${unsupported.join(", ")} are not supported yet and are not advertised`
|
|
58744
|
+
);
|
|
58745
|
+
}
|
|
58416
58746
|
const handler = createExtAppsMessageHandler({
|
|
58417
58747
|
context: {
|
|
58418
58748
|
sessionId: session.id,
|
|
@@ -58446,6 +58776,9 @@ var init_handle_streamable_http_flow = __esm({
|
|
|
58446
58776
|
this.handled();
|
|
58447
58777
|
}
|
|
58448
58778
|
};
|
|
58779
|
+
__decorateClass([
|
|
58780
|
+
Stage41("applyNodeHeaders")
|
|
58781
|
+
], HandleStreamableHttpFlow.prototype, "applyNodeHeaders", 1);
|
|
58449
58782
|
__decorateClass([
|
|
58450
58783
|
Stage41("parseInput")
|
|
58451
58784
|
], HandleStreamableHttpFlow.prototype, "parseInput", 1);
|
|
@@ -60038,7 +60371,7 @@ var init_transport_remote = __esm({
|
|
|
60038
60371
|
|
|
60039
60372
|
// libs/sdk/src/transport/transport.registry.ts
|
|
60040
60373
|
import { getMachineId as getMachineId5, sha256Hex as sha256Hex12 } from "@frontmcp/utils";
|
|
60041
|
-
var TransportService;
|
|
60374
|
+
var SESSION_STORE_RETRY_BASE_MS, SESSION_STORE_RETRY_MAX_MS, TransportService;
|
|
60042
60375
|
var init_transport_registry = __esm({
|
|
60043
60376
|
"libs/sdk/src/transport/transport.registry.ts"() {
|
|
60044
60377
|
"use strict";
|
|
@@ -60051,6 +60384,8 @@ var init_transport_registry = __esm({
|
|
|
60051
60384
|
init_transport_local();
|
|
60052
60385
|
init_transport_remote();
|
|
60053
60386
|
init_transport_types();
|
|
60387
|
+
SESSION_STORE_RETRY_BASE_MS = 1e3;
|
|
60388
|
+
SESSION_STORE_RETRY_MAX_MS = 3e4;
|
|
60054
60389
|
TransportService = class {
|
|
60055
60390
|
ready;
|
|
60056
60391
|
byType = /* @__PURE__ */ new Map();
|
|
@@ -60107,8 +60442,28 @@ var init_transport_registry = __esm({
|
|
|
60107
60442
|
* Returns undefined if persistence is disabled or not configured.
|
|
60108
60443
|
*/
|
|
60109
60444
|
getDefaultTtlMs() {
|
|
60445
|
+
if (this.resolvedTtlMs !== void 0) return this.resolvedTtlMs;
|
|
60110
60446
|
return typeof this.persistenceConfig === "object" ? this.persistenceConfig?.defaultTtlMs : void 0;
|
|
60111
60447
|
}
|
|
60448
|
+
/**
|
|
60449
|
+
* TTL resolved from `persistence.defaultTtlMs`, then `persistence.redis.defaultTtlMs`, then 1 hour.
|
|
60450
|
+
* Set only when a persistence backend is configured.
|
|
60451
|
+
*/
|
|
60452
|
+
resolvedTtlMs;
|
|
60453
|
+
/** Store config kept after a successful create so a failed ping can be retried. */
|
|
60454
|
+
storeRetryConfig;
|
|
60455
|
+
storeRetryTimer;
|
|
60456
|
+
storeRetryAttempt = 0;
|
|
60457
|
+
destroyed = false;
|
|
60458
|
+
/** Last time a request refreshed the stored session's TTL, by session id. */
|
|
60459
|
+
lastTtlRefreshAt = /* @__PURE__ */ new Map();
|
|
60460
|
+
/**
|
|
60461
|
+
* Redis key prefix under which session records are stored.
|
|
60462
|
+
* The session store appends `session:` to the transport key prefix.
|
|
60463
|
+
*/
|
|
60464
|
+
getSessionKeyPrefix() {
|
|
60465
|
+
return `${this.transportKeyPrefix}session:`;
|
|
60466
|
+
}
|
|
60112
60467
|
constructor(scope, persistenceConfig, bus) {
|
|
60113
60468
|
this.scope = scope;
|
|
60114
60469
|
this.persistenceConfig = persistenceConfig;
|
|
@@ -60119,10 +60474,11 @@ var init_transport_registry = __esm({
|
|
|
60119
60474
|
this.backendKind = "sqlite";
|
|
60120
60475
|
const sqliteConfig = persistenceConfig.sqlite;
|
|
60121
60476
|
this.transportKeyPrefix = "mcp:transport:";
|
|
60477
|
+
this.resolvedTtlMs = persistenceConfig.defaultTtlMs ?? 36e5;
|
|
60122
60478
|
this.pendingStoreConfig = {
|
|
60123
60479
|
sqlite: sqliteConfig,
|
|
60124
60480
|
keyPrefix: this.transportKeyPrefix,
|
|
60125
|
-
defaultTtlMs:
|
|
60481
|
+
defaultTtlMs: this.resolvedTtlMs
|
|
60126
60482
|
};
|
|
60127
60483
|
this.scope.logger.info("[TransportService] sqlite session store will be initialized for transport persistence");
|
|
60128
60484
|
} else if (persistenceConfig !== false && persistenceConfig?.redis) {
|
|
@@ -60131,43 +60487,23 @@ var init_transport_registry = __esm({
|
|
|
60131
60487
|
const providerType = "provider" in redisConfig ? redisConfig.provider : "redis";
|
|
60132
60488
|
this.backendKind = providerType === "vercel-kv" ? "vercel-kv" : "redis";
|
|
60133
60489
|
this.transportKeyPrefix = redisConfig.keyPrefix ?? "mcp:transport:";
|
|
60490
|
+
this.resolvedTtlMs = persistenceConfig.defaultTtlMs ?? redisConfig.defaultTtlMs ?? 36e5;
|
|
60134
60491
|
this.pendingStoreConfig = {
|
|
60135
60492
|
...redisConfig,
|
|
60136
60493
|
keyPrefix: this.transportKeyPrefix,
|
|
60137
|
-
defaultTtlMs:
|
|
60138
|
-
// 1 hour default
|
|
60494
|
+
defaultTtlMs: this.resolvedTtlMs
|
|
60139
60495
|
};
|
|
60140
60496
|
this.scope.logger.info(
|
|
60141
60497
|
`[TransportService] ${providerType} session store will be initialized for transport persistence`
|
|
60142
60498
|
);
|
|
60143
60499
|
}
|
|
60500
|
+
this.storeRetryConfig = this.pendingStoreConfig;
|
|
60144
60501
|
this.ready = this.initialize();
|
|
60145
60502
|
}
|
|
60146
60503
|
async initialize() {
|
|
60147
60504
|
if (this.pendingStoreConfig) {
|
|
60148
|
-
|
|
60149
|
-
|
|
60150
|
-
this.sessionStore = store;
|
|
60151
|
-
this.pendingStoreConfig = void 0;
|
|
60152
|
-
} catch (error) {
|
|
60153
|
-
const err = error;
|
|
60154
|
-
this.scope.logger.error("[TransportService] Failed to create session store - session persistence disabled", {
|
|
60155
|
-
error: err.message,
|
|
60156
|
-
cause: err.cause?.message
|
|
60157
|
-
});
|
|
60158
|
-
}
|
|
60159
|
-
}
|
|
60160
|
-
if (this.sessionStore) {
|
|
60161
|
-
const isConnected = this.sessionStore.ping ? await this.sessionStore.ping() : true;
|
|
60162
|
-
if (!isConnected) {
|
|
60163
|
-
const providerType = this.backendKind ?? "redis";
|
|
60164
|
-
this.scope.logger.error(
|
|
60165
|
-
`[TransportService] Failed to connect to ${providerType} - session persistence disabled`
|
|
60166
|
-
);
|
|
60167
|
-
await this.teardownSessionStore().catch(() => void 0);
|
|
60168
|
-
} else {
|
|
60169
|
-
this.scope.logger.info("[TransportService] Session store connection validated successfully");
|
|
60170
|
-
}
|
|
60505
|
+
const connected = await this.connectSessionStore();
|
|
60506
|
+
if (!connected && this.backendKind !== "sqlite") this.scheduleSessionStoreRetry();
|
|
60171
60507
|
}
|
|
60172
60508
|
await this.scope.registryFlows(
|
|
60173
60509
|
HandleStreamableHttpFlow,
|
|
@@ -60176,7 +60512,70 @@ var init_transport_registry = __esm({
|
|
|
60176
60512
|
HandleMcp20260728Flow
|
|
60177
60513
|
);
|
|
60178
60514
|
}
|
|
60515
|
+
/**
|
|
60516
|
+
* Create the session store and validate the connection.
|
|
60517
|
+
* Returns true when the store is usable; on failure the store is left undefined.
|
|
60518
|
+
*/
|
|
60519
|
+
async connectSessionStore() {
|
|
60520
|
+
const storeConfig = this.pendingStoreConfig ?? this.storeRetryConfig;
|
|
60521
|
+
if (!storeConfig) return false;
|
|
60522
|
+
try {
|
|
60523
|
+
const store = await createSessionStore(storeConfig, this.scope.logger.child("SessionStore"));
|
|
60524
|
+
this.sessionStore = store;
|
|
60525
|
+
this.pendingStoreConfig = void 0;
|
|
60526
|
+
} catch (error) {
|
|
60527
|
+
const err = error;
|
|
60528
|
+
this.scope.logger.error("[TransportService] Failed to create session store - session persistence disabled", {
|
|
60529
|
+
error: err.message,
|
|
60530
|
+
cause: err.cause?.message
|
|
60531
|
+
});
|
|
60532
|
+
return false;
|
|
60533
|
+
}
|
|
60534
|
+
const isConnected = this.sessionStore?.ping ? await this.sessionStore.ping() : true;
|
|
60535
|
+
if (!isConnected) {
|
|
60536
|
+
const providerType = this.backendKind ?? "redis";
|
|
60537
|
+
this.scope.logger.error(`[TransportService] Failed to connect to ${providerType} - session persistence disabled`);
|
|
60538
|
+
await this.teardownSessionStore().catch(() => void 0);
|
|
60539
|
+
return false;
|
|
60540
|
+
}
|
|
60541
|
+
this.scope.logger.info("[TransportService] Session store connection validated successfully");
|
|
60542
|
+
return true;
|
|
60543
|
+
}
|
|
60544
|
+
/**
|
|
60545
|
+
* Retry the session store with exponential backoff (1s doubling to 30s) so a Redis that
|
|
60546
|
+
* was down at startup is picked up when it comes back, and `/readyz` turns healthy.
|
|
60547
|
+
*/
|
|
60548
|
+
scheduleSessionStoreRetry() {
|
|
60549
|
+
if (this.destroyed || this.storeRetryTimer) return;
|
|
60550
|
+
const delay = Math.min(SESSION_STORE_RETRY_MAX_MS, SESSION_STORE_RETRY_BASE_MS * 2 ** this.storeRetryAttempt);
|
|
60551
|
+
this.storeRetryAttempt++;
|
|
60552
|
+
this.storeRetryTimer = setTimeout(() => {
|
|
60553
|
+
this.storeRetryTimer = void 0;
|
|
60554
|
+
void this.retrySessionStore();
|
|
60555
|
+
}, delay);
|
|
60556
|
+
this.storeRetryTimer.unref?.();
|
|
60557
|
+
}
|
|
60558
|
+
async retrySessionStore() {
|
|
60559
|
+
if (this.destroyed || this.sessionStore) return;
|
|
60560
|
+
const connected = await this.connectSessionStore().catch(() => false);
|
|
60561
|
+
if (this.destroyed) {
|
|
60562
|
+
await this.teardownSessionStore().catch(() => void 0);
|
|
60563
|
+
return;
|
|
60564
|
+
}
|
|
60565
|
+
if (connected) {
|
|
60566
|
+
this.storeRetryAttempt = 0;
|
|
60567
|
+
this.scope.logger.info("[TransportService] Session store recovered after startup failure");
|
|
60568
|
+
return;
|
|
60569
|
+
}
|
|
60570
|
+
this.scheduleSessionStoreRetry();
|
|
60571
|
+
}
|
|
60179
60572
|
async destroy() {
|
|
60573
|
+
this.destroyed = true;
|
|
60574
|
+
if (this.storeRetryTimer) {
|
|
60575
|
+
clearTimeout(this.storeRetryTimer);
|
|
60576
|
+
this.storeRetryTimer = void 0;
|
|
60577
|
+
}
|
|
60578
|
+
this.lastTtlRefreshAt.clear();
|
|
60180
60579
|
await this.teardownSessionStore();
|
|
60181
60580
|
}
|
|
60182
60581
|
/**
|
|
@@ -60237,10 +60636,33 @@ var init_transport_registry = __esm({
|
|
|
60237
60636
|
getBackendKind() {
|
|
60238
60637
|
return this.backendKind;
|
|
60239
60638
|
}
|
|
60639
|
+
/**
|
|
60640
|
+
* Slide the stored session's TTL while this instance is serving it. Redis and Vercel KV
|
|
60641
|
+
* session stores extend the TTL on read, so a plain `get` is enough. Throttled per session
|
|
60642
|
+
* to a quarter of the TTL, fire-and-forget.
|
|
60643
|
+
*/
|
|
60644
|
+
refreshStoredSessionTtl(sessionId) {
|
|
60645
|
+
const store = this.sessionStore;
|
|
60646
|
+
const ttlMs = this.getDefaultTtlMs();
|
|
60647
|
+
if (!store || !ttlMs || this.backendKind !== "redis" && this.backendKind !== "vercel-kv") return;
|
|
60648
|
+
const now = Date.now();
|
|
60649
|
+
const last = this.lastTtlRefreshAt.get(sessionId);
|
|
60650
|
+
if (last !== void 0 && now - last < Math.max(1e3, Math.floor(ttlMs / 4))) return;
|
|
60651
|
+
this.lastTtlRefreshAt.set(sessionId, now);
|
|
60652
|
+
store.get(sessionId).catch((err) => {
|
|
60653
|
+
this.scope.logger.warn("[TransportService] Failed to refresh session TTL", {
|
|
60654
|
+
sessionId: sessionId.slice(0, 20),
|
|
60655
|
+
error: err instanceof Error ? err.message : String(err)
|
|
60656
|
+
});
|
|
60657
|
+
});
|
|
60658
|
+
}
|
|
60240
60659
|
async getTransporter(type, token, sessionId) {
|
|
60241
60660
|
const key = this.keyOf(type, token, sessionId);
|
|
60242
60661
|
const local = this.lookupLocal(key);
|
|
60243
|
-
if (local)
|
|
60662
|
+
if (local) {
|
|
60663
|
+
this.refreshStoredSessionTtl(sessionId);
|
|
60664
|
+
return local;
|
|
60665
|
+
}
|
|
60244
60666
|
if (this.distributed && this.bus) {
|
|
60245
60667
|
const location = await this.bus.lookup(key);
|
|
60246
60668
|
if (location) {
|
|
@@ -60335,7 +60757,7 @@ var init_transport_registry = __esm({
|
|
|
60335
60757
|
});
|
|
60336
60758
|
const currentNodeId = getMachineId5();
|
|
60337
60759
|
if (this.scope.haManager && storedSession.session.nodeId && storedSession.session.nodeId !== currentNodeId) {
|
|
60338
|
-
const sessionKey = `${this.
|
|
60760
|
+
const sessionKey = `${this.getSessionKeyPrefix()}${sessionId}`;
|
|
60339
60761
|
const result = await this.scope.haManager.attemptTakeover(sessionKey, storedSession.session.nodeId);
|
|
60340
60762
|
if (!result.claimed) {
|
|
60341
60763
|
this.scope.logger.debug("[HA] Session already claimed by another pod", { sessionId: sessionId.slice(0, 20) });
|
|
@@ -60668,6 +61090,7 @@ var init_transport_registry = __esm({
|
|
|
60668
61090
|
}
|
|
60669
61091
|
}
|
|
60670
61092
|
evictLocal(key) {
|
|
61093
|
+
this.lastTtlRefreshAt.delete(key.sessionId);
|
|
60671
61094
|
const typeBucket = this.byType.get(key.type);
|
|
60672
61095
|
if (!typeBucket) return;
|
|
60673
61096
|
const tokenBucket = typeBucket.get(key.tokenHash);
|
|
@@ -61624,7 +62047,7 @@ var init_http_request_flow = __esm({
|
|
|
61624
62047
|
// libs/sdk/src/scope/scope.instance.ts
|
|
61625
62048
|
import "reflect-metadata";
|
|
61626
62049
|
import { createGuardManager } from "@frontmcp/guard";
|
|
61627
|
-
import { getEnvFlag, getMachineId as getMachineId6, getRuntimeContext as getRuntimeContext15, isEdgeRuntime } from "@frontmcp/utils";
|
|
62050
|
+
import { createRedisClient, getEnvFlag, getMachineId as getMachineId6, getRuntimeContext as getRuntimeContext15, isEdgeRuntime } from "@frontmcp/utils";
|
|
61628
62051
|
var SCOPE_NON_DISPATCHABLE_FLOWS, Scope;
|
|
61629
62052
|
var init_scope_instance = __esm({
|
|
61630
62053
|
"libs/sdk/src/scope/scope.instance.ts"() {
|
|
@@ -61669,6 +62092,7 @@ var init_scope_instance = __esm({
|
|
|
61669
62092
|
init_tool_registry();
|
|
61670
62093
|
init_tool_utils();
|
|
61671
62094
|
init_ui();
|
|
62095
|
+
init_ui_option_warnings();
|
|
61672
62096
|
init_bus();
|
|
61673
62097
|
init_event_stores();
|
|
61674
62098
|
init_request_state();
|
|
@@ -61695,6 +62119,8 @@ var init_scope_instance = __esm({
|
|
|
61695
62119
|
transportService;
|
|
61696
62120
|
// TODO: migrate transport service to transport.registry
|
|
61697
62121
|
notificationService;
|
|
62122
|
+
/** ioredis client owned by the HA manager and transport bus; closed in shutdown(). */
|
|
62123
|
+
haRedisClient;
|
|
61698
62124
|
toolUIRegistry;
|
|
61699
62125
|
authUiRegistry;
|
|
61700
62126
|
entryPath;
|
|
@@ -61775,7 +62201,7 @@ var init_scope_instance = __esm({
|
|
|
61775
62201
|
const isDistributed = getRuntimeContext15().deployment === "distributed";
|
|
61776
62202
|
if (isDistributed && this.metadata.redis && !this.cliMode) {
|
|
61777
62203
|
try {
|
|
61778
|
-
const haRedis = this.
|
|
62204
|
+
const haRedis = this.createHaRedisClient();
|
|
61779
62205
|
this.haManager = HaManager.create({
|
|
61780
62206
|
redis: haRedis,
|
|
61781
62207
|
nodeId: getMachineId6(),
|
|
@@ -61785,11 +62211,13 @@ var init_scope_instance = __esm({
|
|
|
61785
62211
|
this.logger.info("[HA] Manager started for distributed deployment");
|
|
61786
62212
|
} catch (err) {
|
|
61787
62213
|
this.logger.warn("[HA] Failed to start HA manager \u2014 running without HA", { error: err });
|
|
62214
|
+
this.haManager = void 0;
|
|
62215
|
+
await this.closeHaRedisClient();
|
|
61788
62216
|
}
|
|
61789
62217
|
}
|
|
61790
62218
|
let transportBus;
|
|
61791
|
-
if (this.haManager &&
|
|
61792
|
-
transportBus = new RedisTransportBus(this.
|
|
62219
|
+
if (this.haManager && this.haRedisClient && isDistributed) {
|
|
62220
|
+
transportBus = new RedisTransportBus(this.haRedisClient, getMachineId6(), {
|
|
61793
62221
|
logger: this.logger
|
|
61794
62222
|
});
|
|
61795
62223
|
this.logger.info("[HA] Transport bus created for distributed session routing");
|
|
@@ -61801,11 +62229,9 @@ var init_scope_instance = __esm({
|
|
|
61801
62229
|
}
|
|
61802
62230
|
this.transportService = new TransportService(this, effectivePersistence, transportBus);
|
|
61803
62231
|
warnIfRequestStateKeyNotShared({ logger: this.logger, metadata: this.metadata });
|
|
61804
|
-
if (this.haManager && isDistributed) {
|
|
61805
|
-
const persistCfg = transportConfig?.persistence;
|
|
61806
|
-
const resolvedPrefix = persistCfg && typeof persistCfg === "object" && persistCfg.redis?.keyPrefix ? persistCfg.redis.keyPrefix : "mcp:transport:";
|
|
62232
|
+
if (this.haManager && isDistributed && this.transportService.getBackendKind() === "redis") {
|
|
61807
62233
|
this.haManager.startOrphanScanner({
|
|
61808
|
-
sessionKeyPrefix:
|
|
62234
|
+
sessionKeyPrefix: this.transportService.getSessionKeyPrefix(),
|
|
61809
62235
|
onOrphan: (sessionId, previousNodeId) => {
|
|
61810
62236
|
this.logger.info(
|
|
61811
62237
|
`[HA] Orphaned session ${sessionId.slice(0, 20)} from ${previousNodeId} \u2014 available for recreation`
|
|
@@ -61861,7 +62287,15 @@ var init_scope_instance = __esm({
|
|
|
61861
62287
|
"[tasks] Background tasks are unavailable on this edge/serverless runtime: the in-process runner cannot continue past the HTTP response, and an in-memory store is not shared between isolates. Serving without tasks. Configure `tasks.redis` (Upstash Redis over HTTP works on Workers) to enable them."
|
|
61862
62288
|
);
|
|
61863
62289
|
}
|
|
61864
|
-
const
|
|
62290
|
+
const gateTasksRedis = tasksConfig?.redis ?? this.metadata.redis;
|
|
62291
|
+
const tasksUseSqlite = Boolean(tasksConfig?.sqlite ?? (gateTasksRedis ? void 0 : resolvedTopLevelSqlite));
|
|
62292
|
+
const tasksUnavailableOnVercelKv = !tasksExplicitlyEnabled && !tasksUseSqlite && resolvesToVercelKvTaskBackend(gateTasksRedis);
|
|
62293
|
+
if (tasksUnavailableOnVercelKv && !tasksExplicitlyDisabled) {
|
|
62294
|
+
this.logger.warn(
|
|
62295
|
+
"[tasks] Background tasks are disabled: Vercel KV has no pub/sub, which task results and cancellation require. Serving without tasks. Configure `tasks.redis` with Redis or Upstash to enable them."
|
|
62296
|
+
);
|
|
62297
|
+
}
|
|
62298
|
+
const shouldInitTasks = !tasksExplicitlyDisabled && !tasksUnavailableOnEdge && !tasksUnavailableOnVercelKv && (!this.cliMode || tasksEnabledForCli || isTaskWorker);
|
|
61865
62299
|
const tasksPromise = shouldInitTasks ? (async () => {
|
|
61866
62300
|
const tasksRedis = tasksConfig?.redis ?? this.metadata.redis;
|
|
61867
62301
|
const tasksSqlite = tasksConfig?.sqlite ?? (tasksRedis ? void 0 : resolvedTopLevelSqlite);
|
|
@@ -62360,6 +62794,16 @@ var init_scope_instance = __esm({
|
|
|
62360
62794
|
async compileUIWidgets() {
|
|
62361
62795
|
const toolsWithUI = this.scopeTools.getTools(true).filter((t) => hasUIConfig(t.metadata));
|
|
62362
62796
|
if (toolsWithUI.length === 0) return;
|
|
62797
|
+
for (const tool of toolsWithUI) {
|
|
62798
|
+
const resourceUri = tool.metadata.ui?.resourceUri;
|
|
62799
|
+
this.toolUIRegistry.registerTool(tool.metadata.name, typeof resourceUri === "string" ? resourceUri : void 0);
|
|
62800
|
+
for (const message of describeIgnoredUiOptions(
|
|
62801
|
+
tool.metadata.name,
|
|
62802
|
+
tool.metadata.ui
|
|
62803
|
+
)) {
|
|
62804
|
+
this.logger.warn(message);
|
|
62805
|
+
}
|
|
62806
|
+
}
|
|
62363
62807
|
this.scopeResources.registerDynamicResource(StaticWidgetResourceTemplate);
|
|
62364
62808
|
this.logger.verbose(`Registered UI resource template for ${toolsWithUI.length} tool(s) with UI configs`);
|
|
62365
62809
|
const staticModeTools = toolsWithUI.filter(
|
|
@@ -62938,6 +63382,29 @@ var init_scope_instance = __esm({
|
|
|
62938
63382
|
}
|
|
62939
63383
|
throw new FlowExitedWithoutOutputError();
|
|
62940
63384
|
}
|
|
63385
|
+
/**
|
|
63386
|
+
* Build the ioredis client shared by the HA manager and the transport bus from `metadata.redis`.
|
|
63387
|
+
* The client reconnects on its own and has a permanent, rate-limited 'error' listener.
|
|
63388
|
+
*/
|
|
63389
|
+
createHaRedisClient() {
|
|
63390
|
+
const redis = this.metadata.redis;
|
|
63391
|
+
if (!redis || redis.provider === "vercel-kv") {
|
|
63392
|
+
throw new Error("Distributed HA requires a TCP Redis (ioredis); Vercel KV over REST has no Lua/pub-sub support");
|
|
63393
|
+
}
|
|
63394
|
+
const client = createRedisClient({ ...redis, label: "ha", logger: this.logger });
|
|
63395
|
+
this.haRedisClient = client;
|
|
63396
|
+
return client;
|
|
63397
|
+
}
|
|
63398
|
+
async closeHaRedisClient() {
|
|
63399
|
+
const client = this.haRedisClient;
|
|
63400
|
+
if (!client) return;
|
|
63401
|
+
this.haRedisClient = void 0;
|
|
63402
|
+
try {
|
|
63403
|
+
await client.quit();
|
|
63404
|
+
} catch {
|
|
63405
|
+
client.disconnect();
|
|
63406
|
+
}
|
|
63407
|
+
}
|
|
62941
63408
|
/**
|
|
62942
63409
|
* Shut down the scope and release resources.
|
|
62943
63410
|
* Disconnects channel service connectors, unsubscribes event handlers,
|
|
@@ -62951,6 +63418,7 @@ var init_scope_instance = __esm({
|
|
|
62951
63418
|
this.logger.error("[HA] Manager shutdown failed", { error: err });
|
|
62952
63419
|
}
|
|
62953
63420
|
}
|
|
63421
|
+
await this.closeHaRedisClient();
|
|
62954
63422
|
if (this._channelTeardown) {
|
|
62955
63423
|
try {
|
|
62956
63424
|
await this._channelTeardown();
|
|
@@ -64079,6 +64547,177 @@ var init_scope_registry = __esm({
|
|
|
64079
64547
|
}
|
|
64080
64548
|
});
|
|
64081
64549
|
|
|
64550
|
+
// libs/sdk/src/transport/ipc-server.transport.ts
|
|
64551
|
+
function isIpcStdioRequested() {
|
|
64552
|
+
return !!process.env["FRONTMCP_DEV_STDIO_FD"] && typeof process.send === "function";
|
|
64553
|
+
}
|
|
64554
|
+
var IPC_READY_MESSAGE, IpcServerTransport;
|
|
64555
|
+
var init_ipc_server_transport = __esm({
|
|
64556
|
+
"libs/sdk/src/transport/ipc-server.transport.ts"() {
|
|
64557
|
+
"use strict";
|
|
64558
|
+
IPC_READY_MESSAGE = { __frontmcp: "ready" };
|
|
64559
|
+
IpcServerTransport = class {
|
|
64560
|
+
onclose;
|
|
64561
|
+
onerror;
|
|
64562
|
+
onmessage;
|
|
64563
|
+
started = false;
|
|
64564
|
+
handleMessage = (raw) => {
|
|
64565
|
+
if (!raw || typeof raw !== "object" || raw.__frontmcp !== void 0) return;
|
|
64566
|
+
this.onmessage?.(raw);
|
|
64567
|
+
};
|
|
64568
|
+
handleDisconnect = () => {
|
|
64569
|
+
this.onclose?.();
|
|
64570
|
+
};
|
|
64571
|
+
async start() {
|
|
64572
|
+
if (this.started) throw new Error("IpcServerTransport already started");
|
|
64573
|
+
if (typeof process.send !== "function") {
|
|
64574
|
+
throw new Error("IpcServerTransport requires a process forked with an IPC channel");
|
|
64575
|
+
}
|
|
64576
|
+
this.started = true;
|
|
64577
|
+
process.on("message", this.handleMessage);
|
|
64578
|
+
process.once("disconnect", this.handleDisconnect);
|
|
64579
|
+
process.send(IPC_READY_MESSAGE);
|
|
64580
|
+
}
|
|
64581
|
+
async send(message) {
|
|
64582
|
+
const send = process.send;
|
|
64583
|
+
if (!process.connected || typeof send !== "function") {
|
|
64584
|
+
throw new Error("IPC channel is closed");
|
|
64585
|
+
}
|
|
64586
|
+
await new Promise((resolve, reject) => {
|
|
64587
|
+
send.call(process, message, (err) => err ? reject(err) : resolve());
|
|
64588
|
+
});
|
|
64589
|
+
}
|
|
64590
|
+
async close() {
|
|
64591
|
+
process.off("message", this.handleMessage);
|
|
64592
|
+
process.off("disconnect", this.handleDisconnect);
|
|
64593
|
+
this.started = false;
|
|
64594
|
+
this.onclose?.();
|
|
64595
|
+
}
|
|
64596
|
+
};
|
|
64597
|
+
}
|
|
64598
|
+
});
|
|
64599
|
+
|
|
64600
|
+
// libs/sdk/src/server/middleware/csp.middleware.ts
|
|
64601
|
+
import { getEnv as getEnv8, getEnvFlag as getEnvFlag2 } from "@frontmcp/utils";
|
|
64602
|
+
function readCspFromEnv() {
|
|
64603
|
+
if (!getEnvFlag2("FRONTMCP_CSP_ENABLED")) return void 0;
|
|
64604
|
+
const rawDirectives = getEnv8("FRONTMCP_CSP_DIRECTIVES") ?? "";
|
|
64605
|
+
const directives = {};
|
|
64606
|
+
for (const part of rawDirectives.split(";")) {
|
|
64607
|
+
const trimmed = part.trim();
|
|
64608
|
+
if (!trimmed) continue;
|
|
64609
|
+
const spaceIdx = trimmed.indexOf(" ");
|
|
64610
|
+
if (spaceIdx > 0) {
|
|
64611
|
+
directives[trimmed.slice(0, spaceIdx)] = trimmed.slice(spaceIdx + 1);
|
|
64612
|
+
} else {
|
|
64613
|
+
directives[trimmed] = "";
|
|
64614
|
+
}
|
|
64615
|
+
}
|
|
64616
|
+
return {
|
|
64617
|
+
enabled: true,
|
|
64618
|
+
directives,
|
|
64619
|
+
reportUri: getEnv8("FRONTMCP_CSP_REPORT_URI"),
|
|
64620
|
+
reportOnly: getEnvFlag2("FRONTMCP_CSP_REPORT_ONLY")
|
|
64621
|
+
};
|
|
64622
|
+
}
|
|
64623
|
+
function buildCspHeaderValue(options) {
|
|
64624
|
+
const parts = [];
|
|
64625
|
+
for (const [directive, value] of Object.entries(options.directives)) {
|
|
64626
|
+
parts.push(value ? `${directive} ${value}` : directive);
|
|
64627
|
+
}
|
|
64628
|
+
if (options.reportUri) {
|
|
64629
|
+
parts.push(`report-uri ${options.reportUri}`);
|
|
64630
|
+
}
|
|
64631
|
+
return parts.join("; ");
|
|
64632
|
+
}
|
|
64633
|
+
function getCspHeaderName(reportOnly) {
|
|
64634
|
+
return reportOnly ? "Content-Security-Policy-Report-Only" : "Content-Security-Policy";
|
|
64635
|
+
}
|
|
64636
|
+
function readSecurityHeadersFromEnv() {
|
|
64637
|
+
return {
|
|
64638
|
+
hsts: readHeaderEnv("FRONTMCP_HSTS"),
|
|
64639
|
+
contentTypeOptions: readHeaderEnv("FRONTMCP_CONTENT_TYPE_OPTIONS", "nosniff"),
|
|
64640
|
+
frameOptions: readHeaderEnv("FRONTMCP_FRAME_OPTIONS", "DENY"),
|
|
64641
|
+
custom: readCustomHeadersFromEnv()
|
|
64642
|
+
};
|
|
64643
|
+
}
|
|
64644
|
+
function readHeaderEnv(name44, fallback) {
|
|
64645
|
+
const value = getEnv8(name44);
|
|
64646
|
+
if (value === void 0 || value === "") return fallback;
|
|
64647
|
+
return ["off", "false", "none"].includes(value.trim().toLowerCase()) ? void 0 : value;
|
|
64648
|
+
}
|
|
64649
|
+
function readCustomHeadersFromEnv() {
|
|
64650
|
+
const raw = getEnv8("FRONTMCP_HEADERS_CUSTOM");
|
|
64651
|
+
if (!raw) return void 0;
|
|
64652
|
+
try {
|
|
64653
|
+
const parsed = JSON.parse(raw);
|
|
64654
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return void 0;
|
|
64655
|
+
return Object.fromEntries(
|
|
64656
|
+
Object.entries(parsed).filter((entry) => typeof entry[1] === "string")
|
|
64657
|
+
);
|
|
64658
|
+
} catch {
|
|
64659
|
+
return void 0;
|
|
64660
|
+
}
|
|
64661
|
+
}
|
|
64662
|
+
function applySecurityHeaders(res, headers, csp) {
|
|
64663
|
+
if (headers.hsts) {
|
|
64664
|
+
res.setHeader("Strict-Transport-Security", headers.hsts);
|
|
64665
|
+
}
|
|
64666
|
+
if (headers.contentTypeOptions) {
|
|
64667
|
+
res.setHeader("X-Content-Type-Options", headers.contentTypeOptions);
|
|
64668
|
+
}
|
|
64669
|
+
if (headers.frameOptions) {
|
|
64670
|
+
res.setHeader("X-Frame-Options", headers.frameOptions);
|
|
64671
|
+
}
|
|
64672
|
+
if (headers.custom) {
|
|
64673
|
+
for (const [name44, value] of Object.entries(headers.custom)) {
|
|
64674
|
+
res.setHeader(name44, value);
|
|
64675
|
+
}
|
|
64676
|
+
}
|
|
64677
|
+
if (csp?.enabled) {
|
|
64678
|
+
const headerName = getCspHeaderName(csp.reportOnly);
|
|
64679
|
+
const headerValue = buildCspHeaderValue(csp);
|
|
64680
|
+
if (headerValue) {
|
|
64681
|
+
res.setHeader(headerName, headerValue);
|
|
64682
|
+
}
|
|
64683
|
+
}
|
|
64684
|
+
}
|
|
64685
|
+
function resolveSecurityHeaders(options = {}) {
|
|
64686
|
+
const env = readSecurityHeadersFromEnv();
|
|
64687
|
+
const pick = (explicit, fromEnv) => explicit === false ? void 0 : explicit ?? fromEnv;
|
|
64688
|
+
const resolved = {
|
|
64689
|
+
hsts: pick(options.hsts, env.hsts),
|
|
64690
|
+
contentTypeOptions: pick(options.contentTypeOptions, env.contentTypeOptions),
|
|
64691
|
+
frameOptions: pick(options.frameOptions, env.frameOptions),
|
|
64692
|
+
custom: { ...env.custom, ...options.custom }
|
|
64693
|
+
};
|
|
64694
|
+
let csp;
|
|
64695
|
+
if (options.csp?.enabled === false) {
|
|
64696
|
+
csp = void 0;
|
|
64697
|
+
} else if (options.csp?.enabled === true) {
|
|
64698
|
+
const directives = {};
|
|
64699
|
+
for (const [name44, value] of Object.entries(options.csp.directives ?? {})) {
|
|
64700
|
+
directives[name44] = Array.isArray(value) ? value.join(" ") : value;
|
|
64701
|
+
}
|
|
64702
|
+
csp = {
|
|
64703
|
+
enabled: true,
|
|
64704
|
+
directives,
|
|
64705
|
+
reportUri: options.csp.reportUri,
|
|
64706
|
+
reportOnly: options.csp.reportOnly ?? false
|
|
64707
|
+
};
|
|
64708
|
+
} else {
|
|
64709
|
+
csp = readCspFromEnv();
|
|
64710
|
+
}
|
|
64711
|
+
const headers = {};
|
|
64712
|
+
applySecurityHeaders({ setHeader: (name44, value) => void (headers[name44] = value) }, resolved, csp);
|
|
64713
|
+
return headers;
|
|
64714
|
+
}
|
|
64715
|
+
var init_csp_middleware = __esm({
|
|
64716
|
+
"libs/sdk/src/server/middleware/csp.middleware.ts"() {
|
|
64717
|
+
"use strict";
|
|
64718
|
+
}
|
|
64719
|
+
});
|
|
64720
|
+
|
|
64082
64721
|
// libs/sdk/src/transport/web-response.renderer.ts
|
|
64083
64722
|
function toByteStream(streamLike) {
|
|
64084
64723
|
const iter = streamLike[Symbol.asyncIterator];
|
|
@@ -64184,6 +64823,7 @@ function isCloudflareWorkersRuntime(request) {
|
|
|
64184
64823
|
return typeof cloudflareProperties === "object" && cloudflareProperties !== null;
|
|
64185
64824
|
}
|
|
64186
64825
|
function resolvePeerAddress(request, ctx) {
|
|
64826
|
+
if (carriedPeerAddresses.has(request)) return carriedPeerAddresses.get(request);
|
|
64187
64827
|
if (typeof ctx?.requestIP === "function") return ctx.requestIP(request)?.address;
|
|
64188
64828
|
if (typeof ctx?.remoteAddr?.hostname === "string") return ctx.remoteAddr.hostname;
|
|
64189
64829
|
if (isCloudflareWorkersRuntime(request)) return request.headers.get("cf-connecting-ip") ?? void 0;
|
|
@@ -64202,7 +64842,13 @@ function mapHttpCors(httpCors, scope) {
|
|
|
64202
64842
|
}
|
|
64203
64843
|
function createWebFetchHandler(scope, options = {}) {
|
|
64204
64844
|
const httpConfig = scope.metadata.http;
|
|
64205
|
-
const
|
|
64845
|
+
const healthConfig = scope.metadata.health ?? {};
|
|
64846
|
+
const healthEnabled = healthConfig.enabled !== false;
|
|
64847
|
+
const healthzPath = healthConfig.healthzPath ?? "/healthz";
|
|
64848
|
+
const readyzPath = healthConfig.readyzPath ?? "/readyz";
|
|
64849
|
+
const readyzEnabled = isReadyzEnabled(healthConfig);
|
|
64850
|
+
const bodyLimit = parseByteLimit(httpConfig?.bodyLimit);
|
|
64851
|
+
const securityHeaders = resolveSecurityHeaders(httpConfig?.securityHeaders);
|
|
64206
64852
|
const normalizePath = (p) => {
|
|
64207
64853
|
const withSlash = p.startsWith("/") ? p : `/${p}`;
|
|
64208
64854
|
let end = withSlash.length;
|
|
@@ -64246,7 +64892,13 @@ function createWebFetchHandler(scope, options = {}) {
|
|
|
64246
64892
|
for (const [k, v] of Object.entries(corsHeadersFor(request))) headers.set(k, v);
|
|
64247
64893
|
return new Response(response.body, { status: response.status, statusText: response.statusText, headers });
|
|
64248
64894
|
};
|
|
64249
|
-
|
|
64895
|
+
const withSecurityHeaders = (response) => {
|
|
64896
|
+
const headers = new Headers(response.headers);
|
|
64897
|
+
for (const [k, v] of Object.entries(securityHeaders)) if (!headers.has(k)) headers.set(k, v);
|
|
64898
|
+
headers.delete("x-powered-by");
|
|
64899
|
+
return new Response(response.body, { status: response.status, statusText: response.statusText, headers });
|
|
64900
|
+
};
|
|
64901
|
+
const handleRequest = async (request, ctx, env) => {
|
|
64250
64902
|
const url = new URL(request.url);
|
|
64251
64903
|
if (hostValidation) {
|
|
64252
64904
|
const rejection = validateHostHeaders(
|
|
@@ -64261,17 +64913,34 @@ function createWebFetchHandler(scope, options = {}) {
|
|
|
64261
64913
|
return Response.json({ error: rejection.error, message: rejection.message }, { status: rejection.status });
|
|
64262
64914
|
}
|
|
64263
64915
|
}
|
|
64916
|
+
const limited = await enforceBodyLimit(request, bodyLimit, ctx);
|
|
64917
|
+
if (limited instanceof Response) return withCors(limited, request);
|
|
64918
|
+
request = limited;
|
|
64264
64919
|
if (corsEnabled && request.method === "OPTIONS") {
|
|
64265
64920
|
return new Response(null, { status: 204, headers: corsHeadersFor(request) });
|
|
64266
64921
|
}
|
|
64267
|
-
if (healthPaths.
|
|
64268
|
-
|
|
64269
|
-
|
|
64270
|
-
|
|
64271
|
-
|
|
64272
|
-
|
|
64273
|
-
|
|
64274
|
-
|
|
64922
|
+
if (options.healthPaths ? options.healthPaths.includes(url.pathname) : healthEnabled) {
|
|
64923
|
+
const isReadyz = !options.healthPaths && url.pathname === readyzPath && readyzEnabled;
|
|
64924
|
+
const isHealthz = options.healthPaths ? url.pathname !== "/readyz" : url.pathname === healthzPath || url.pathname === "/health";
|
|
64925
|
+
if (isReadyz && scope.healthService) {
|
|
64926
|
+
const readiness = await scope.healthService.getReadyz();
|
|
64927
|
+
return withCors(
|
|
64928
|
+
Response.json(
|
|
64929
|
+
{ ...readiness, transport: "web-fetch" },
|
|
64930
|
+
{ status: readiness.status === "ready" ? 200 : 503, headers: { "Cache-Control": "no-store" } }
|
|
64931
|
+
),
|
|
64932
|
+
request
|
|
64933
|
+
);
|
|
64934
|
+
}
|
|
64935
|
+
if (isHealthz || isReadyz || options.healthPaths) {
|
|
64936
|
+
return withCors(
|
|
64937
|
+
Response.json(
|
|
64938
|
+
{ status: "ok", server: scope.metadata.info, transport: "web-fetch" },
|
|
64939
|
+
{ headers: { "Cache-Control": "no-store" } }
|
|
64940
|
+
),
|
|
64941
|
+
request
|
|
64942
|
+
);
|
|
64943
|
+
}
|
|
64275
64944
|
}
|
|
64276
64945
|
if (!entryPaths.has(normalizePath(url.pathname))) {
|
|
64277
64946
|
const authResponse = await runMatchingHttpFlowWeb(scope, request, { ctx, env });
|
|
@@ -64285,6 +64954,53 @@ function createWebFetchHandler(scope, options = {}) {
|
|
|
64285
64954
|
const rendered = await runHttpRequestFlowWeb(scope, request, { ctx, env });
|
|
64286
64955
|
return withCors(rendered ?? Response.json({ error: "Not Found" }, { status: 404 }), request);
|
|
64287
64956
|
};
|
|
64957
|
+
return async function handle(request, ctx, env) {
|
|
64958
|
+
return withSecurityHeaders(await handleRequest(request, ctx, env));
|
|
64959
|
+
};
|
|
64960
|
+
}
|
|
64961
|
+
function parseByteLimit(limit) {
|
|
64962
|
+
if (typeof limit === "number" && Number.isFinite(limit) && limit >= 0) return limit;
|
|
64963
|
+
if (typeof limit === "string") {
|
|
64964
|
+
const match = /^(\d+(?:\.\d+)?)\s*(b|kb|mb|gb)?$/i.exec(limit.trim());
|
|
64965
|
+
if (match) return Math.floor(Number(match[1]) * BYTE_UNITS[(match[2] ?? "b").toLowerCase()]);
|
|
64966
|
+
}
|
|
64967
|
+
return DEFAULT_BODY_LIMIT_BYTES;
|
|
64968
|
+
}
|
|
64969
|
+
function payloadTooLarge(limit, length) {
|
|
64970
|
+
const error = new PayloadTooLargeError(limit, length);
|
|
64971
|
+
return Response.json({ jsonrpc: "2.0", error: error.toJsonRpcError(), id: null }, { status: 413 });
|
|
64972
|
+
}
|
|
64973
|
+
async function enforceBodyLimit(request, limit, ctx) {
|
|
64974
|
+
const method = request.method.toUpperCase();
|
|
64975
|
+
if (method === "GET" || method === "HEAD" || method === "OPTIONS" || !request.body) return request;
|
|
64976
|
+
const declared = request.headers.get("content-length");
|
|
64977
|
+
if (declared !== null) {
|
|
64978
|
+
const length = Number(declared);
|
|
64979
|
+
if (Number.isFinite(length) && length > limit) return payloadTooLarge(limit, length);
|
|
64980
|
+
}
|
|
64981
|
+
const peerAddress = resolvePeerAddress(request, ctx);
|
|
64982
|
+
const reader = request.body.getReader();
|
|
64983
|
+
const chunks = [];
|
|
64984
|
+
let total = 0;
|
|
64985
|
+
for (; ; ) {
|
|
64986
|
+
const { done, value } = await reader.read();
|
|
64987
|
+
if (done) break;
|
|
64988
|
+
total += value.byteLength;
|
|
64989
|
+
if (total > limit) {
|
|
64990
|
+
await reader.cancel().catch(() => void 0);
|
|
64991
|
+
return payloadTooLarge(limit, total);
|
|
64992
|
+
}
|
|
64993
|
+
chunks.push(value);
|
|
64994
|
+
}
|
|
64995
|
+
const buffered = new Uint8Array(total);
|
|
64996
|
+
let offset = 0;
|
|
64997
|
+
for (const chunk of chunks) {
|
|
64998
|
+
buffered.set(chunk, offset);
|
|
64999
|
+
offset += chunk.byteLength;
|
|
65000
|
+
}
|
|
65001
|
+
const rebuilt = new Request(request.url, { method: request.method, headers: request.headers, body: buffered });
|
|
65002
|
+
carriedPeerAddresses.set(rebuilt, peerAddress);
|
|
65003
|
+
return rebuilt;
|
|
64288
65004
|
}
|
|
64289
65005
|
async function runHttpRequestFlowWeb(scope, request, opts = {}) {
|
|
64290
65006
|
const url = new URL(request.url);
|
|
@@ -64375,22 +65091,7 @@ async function toServerRequest(request, url, ctx, persistent, env) {
|
|
|
64375
65091
|
function misconfigurationResponse(error) {
|
|
64376
65092
|
const misconfiguration = findMisconfiguration(error);
|
|
64377
65093
|
if (!misconfiguration) return void 0;
|
|
64378
|
-
return Response.json(
|
|
64379
|
-
{ error: "server_misconfigured", code: misconfiguration.code, message: misconfiguration.remedy },
|
|
64380
|
-
{ status: 500 }
|
|
64381
|
-
);
|
|
64382
|
-
}
|
|
64383
|
-
function findMisconfiguration(error) {
|
|
64384
|
-
let current = error;
|
|
64385
|
-
for (let depth = 0; current instanceof Error && depth < 5; depth++) {
|
|
64386
|
-
const code = current.code;
|
|
64387
|
-
if (typeof code === "string" && MISCONFIGURATION_REMEDIES[code]) {
|
|
64388
|
-
return { code, remedy: MISCONFIGURATION_REMEDIES[code] };
|
|
64389
|
-
}
|
|
64390
|
-
if (current.name === "ZodError") return { code: "CONFIG_INVALID", remedy: CONFIG_INVALID_REMEDY };
|
|
64391
|
-
current = current.cause;
|
|
64392
|
-
}
|
|
64393
|
-
return void 0;
|
|
65094
|
+
return Response.json(misconfigurationBody(misconfiguration), { status: 500 });
|
|
64394
65095
|
}
|
|
64395
65096
|
function createDeferredServerBuild(build, options = {}) {
|
|
64396
65097
|
const now = options.now ?? Date.now;
|
|
@@ -64445,7 +65146,7 @@ function flowErrorToHttpOutput(error) {
|
|
|
64445
65146
|
kind: "json",
|
|
64446
65147
|
status: 500,
|
|
64447
65148
|
contentType: "application/json; charset=utf-8",
|
|
64448
|
-
body:
|
|
65149
|
+
body: misconfigurationBody(misconfiguration)
|
|
64449
65150
|
};
|
|
64450
65151
|
}
|
|
64451
65152
|
if (error instanceof FlowControl) {
|
|
@@ -64471,7 +65172,7 @@ function flowErrorToHttpOutput(error) {
|
|
|
64471
65172
|
}
|
|
64472
65173
|
return { kind: "text", status: 500, body: "Internal Server Error", contentType: "text/plain; charset=utf-8" };
|
|
64473
65174
|
}
|
|
64474
|
-
var CLOUDFLARE_WORKERS_USER_AGENT,
|
|
65175
|
+
var CLOUDFLARE_WORKERS_USER_AGENT, carriedPeerAddresses, DEFAULT_BODY_LIMIT_BYTES, BYTE_UNITS, STARTUP_RETRY_MIN_MS, STARTUP_RETRY_MAX_MS;
|
|
64475
65176
|
var init_web_fetch_handler = __esm({
|
|
64476
65177
|
"libs/sdk/src/transport/web-fetch-handler.ts"() {
|
|
64477
65178
|
"use strict";
|
|
@@ -64479,18 +65180,15 @@ var init_web_fetch_handler = __esm({
|
|
|
64479
65180
|
init_server_tokens();
|
|
64480
65181
|
init_path_utils();
|
|
64481
65182
|
init_errors();
|
|
65183
|
+
init_misconfiguration();
|
|
65184
|
+
init_health_routes();
|
|
65185
|
+
init_csp_middleware();
|
|
64482
65186
|
init_host_validation();
|
|
64483
65187
|
init_web_response_renderer();
|
|
64484
65188
|
CLOUDFLARE_WORKERS_USER_AGENT = "Cloudflare-Workers";
|
|
64485
|
-
|
|
64486
|
-
|
|
64487
|
-
|
|
64488
|
-
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.",
|
|
64489
|
-
// The startup checks: a server whose entries ask for protection nothing gives them does not start.
|
|
64490
|
-
UNENFORCED_METADATA: "An entry declares a field only a plugin enforces (approval, featureFlag, ...) and no installed plugin that enforces it reaches the entry, so the server refuses to start. Install the plugin or remove the field; the server log names the entries.",
|
|
64491
|
-
AUTH_CONFIGURATION_ERROR: "The auth or authorities configuration is invalid (for example, entries declare authorities and the server has no authorities option, or a rule checks nothing), so the server refuses to start. The server log names the entries."
|
|
64492
|
-
};
|
|
64493
|
-
CONFIG_INVALID_REMEDY = "The FrontMCP configuration failed validation, so the server refuses to start. The server log names the invalid fields.";
|
|
65189
|
+
carriedPeerAddresses = /* @__PURE__ */ new WeakMap();
|
|
65190
|
+
DEFAULT_BODY_LIMIT_BYTES = 4 * 1024 * 1024;
|
|
65191
|
+
BYTE_UNITS = { b: 1, kb: 1024, mb: 1024 ** 2, gb: 1024 ** 3 };
|
|
64494
65192
|
STARTUP_RETRY_MIN_MS = 1e3;
|
|
64495
65193
|
STARTUP_RETRY_MAX_MS = 6e4;
|
|
64496
65194
|
}
|
|
@@ -64572,13 +65270,13 @@ function auditSecurityDefaults(config, isProduction7) {
|
|
|
64572
65270
|
message: `Server bound to ${bindAddress}.`
|
|
64573
65271
|
});
|
|
64574
65272
|
}
|
|
64575
|
-
const dnsProtectionEnabled = config.security?.dnsRebindingProtection?.enabled
|
|
65273
|
+
const dnsProtectionEnabled = config.security?.dnsRebindingProtection?.enabled !== false;
|
|
64576
65274
|
if (!dnsProtectionEnabled) {
|
|
64577
65275
|
findings.push({
|
|
64578
65276
|
level: "warn",
|
|
64579
65277
|
code: "DNS_REBINDING_UNPROTECTED",
|
|
64580
|
-
message: "DNS rebinding protection is disabled.",
|
|
64581
|
-
recommendation: "
|
|
65278
|
+
message: "DNS rebinding protection is disabled (security.dnsRebindingProtection.enabled is false).",
|
|
65279
|
+
recommendation: "Remove `enabled: false`, or set security.dnsRebindingProtection.allowedHosts, to prevent DNS rebinding attacks."
|
|
64582
65280
|
});
|
|
64583
65281
|
} else {
|
|
64584
65282
|
findings.push({
|
|
@@ -64767,6 +65465,7 @@ var init_express_host_adapter = __esm({
|
|
|
64767
65465
|
"libs/sdk/src/server/adapters/express.host.adapter.ts"() {
|
|
64768
65466
|
"use strict";
|
|
64769
65467
|
init_mcp_error();
|
|
65468
|
+
init_csp_middleware();
|
|
64770
65469
|
init_host_validation_middleware();
|
|
64771
65470
|
init_resolve_allowed_hosts();
|
|
64772
65471
|
init_base_host_adapter();
|
|
@@ -64781,6 +65480,12 @@ var init_express_host_adapter = __esm({
|
|
|
64781
65480
|
deriveHostValidation;
|
|
64782
65481
|
constructor(options) {
|
|
64783
65482
|
super();
|
|
65483
|
+
this.app.disable("x-powered-by");
|
|
65484
|
+
const securityHeaders = resolveSecurityHeaders(options?.securityHeaders);
|
|
65485
|
+
this.app.use((_req, res, next) => {
|
|
65486
|
+
for (const [name44, value] of Object.entries(securityHeaders)) res.setHeader(name44, value);
|
|
65487
|
+
next();
|
|
65488
|
+
});
|
|
64784
65489
|
const corsOptions = options?.cors;
|
|
64785
65490
|
const corsEnabled = corsOptions?.origin !== void 0 && corsOptions.origin !== false;
|
|
64786
65491
|
if (corsEnabled) {
|
|
@@ -65008,6 +65713,7 @@ var init_server_instance = __esm({
|
|
|
65008
65713
|
this.host = new ExpressHostAdapter2({
|
|
65009
65714
|
...corsConfig ? { cors: corsConfig } : {},
|
|
65010
65715
|
...this.config.security ? { security: this.config.security } : {},
|
|
65716
|
+
...this.config.securityHeaders ? { securityHeaders: this.config.securityHeaders } : {},
|
|
65011
65717
|
...this.config.bodyLimit !== void 0 ? { bodyLimit: this.config.bodyLimit } : {},
|
|
65012
65718
|
...this.config.urlencodedLimit !== void 0 ? { urlencodedLimit: this.config.urlencodedLimit } : {}
|
|
65013
65719
|
});
|
|
@@ -65332,6 +66038,7 @@ var init_front_mcp = __esm({
|
|
|
65332
66038
|
init_scope_registry();
|
|
65333
66039
|
init_skill_instructions_helper();
|
|
65334
66040
|
init_task();
|
|
66041
|
+
init_ipc_server_transport();
|
|
65335
66042
|
init_web_fetch_handler();
|
|
65336
66043
|
init_front_mcp_providers();
|
|
65337
66044
|
init_static_startup_check();
|
|
@@ -65451,7 +66158,13 @@ var init_front_mcp = __esm({
|
|
|
65451
66158
|
}
|
|
65452
66159
|
}
|
|
65453
66160
|
static async bootstrap(options) {
|
|
65454
|
-
const
|
|
66161
|
+
const sqlitePath = process.env["FRONTMCP_SQLITE_PATH"];
|
|
66162
|
+
const parsedConfig = frontMcpMetadataSchema.parse(
|
|
66163
|
+
sqlitePath ? {
|
|
66164
|
+
...options,
|
|
66165
|
+
sqlite: { ...options.sqlite ?? {}, path: sqlitePath }
|
|
66166
|
+
} : options
|
|
66167
|
+
);
|
|
65455
66168
|
const daemonSocket = process.env["FRONTMCP_DAEMON_SOCKET"];
|
|
65456
66169
|
if (daemonSocket) {
|
|
65457
66170
|
await _FrontMcpInstance2.runUnixSocket({ ...parsedConfig, socketPath: daemonSocket });
|
|
@@ -65828,7 +66541,7 @@ var init_front_mcp = __esm({
|
|
|
65828
66541
|
};
|
|
65829
66542
|
mcpServer.setRequestHandler(handler.requestSchema, wrappedHandler);
|
|
65830
66543
|
}
|
|
65831
|
-
const transport = new StdioServerTransport();
|
|
66544
|
+
const transport = isIpcStdioRequested() ? new IpcServerTransport() : new StdioServerTransport();
|
|
65832
66545
|
startupCleanups.push(() => transport.close());
|
|
65833
66546
|
await mcpServer.connect(transport);
|
|
65834
66547
|
startupSucceeded = true;
|
|
@@ -66081,7 +66794,7 @@ var init_execute_task = __esm({
|
|
|
66081
66794
|
|
|
66082
66795
|
// libs/sdk/src/common/decorators/front-mcp.decorator.ts
|
|
66083
66796
|
import "reflect-metadata";
|
|
66084
|
-
import { getEnvFlag as
|
|
66797
|
+
import { getEnvFlag as getEnvFlag3 } from "@frontmcp/utils";
|
|
66085
66798
|
function getDecoratorConfig(target) {
|
|
66086
66799
|
if (typeof target !== "function") return void 0;
|
|
66087
66800
|
if (typeof Reflect === "undefined") return void 0;
|
|
@@ -66136,9 +66849,9 @@ function FrontMcp(providedMetadata) {
|
|
|
66136
66849
|
Reflect.defineMetadata(FrontMcpTokens[property] ?? property, metadata[property], target);
|
|
66137
66850
|
}
|
|
66138
66851
|
Reflect.defineMetadata(FRONTMCP_CONFIG_METADATA_KEY, metadata, target);
|
|
66139
|
-
const isServerless2 =
|
|
66140
|
-
const isSchemaExtract =
|
|
66141
|
-
const isStdio =
|
|
66852
|
+
const isServerless2 = getEnvFlag3("FRONTMCP_SERVERLESS");
|
|
66853
|
+
const isSchemaExtract = getEnvFlag3("FRONTMCP_SCHEMA_EXTRACT");
|
|
66854
|
+
const isStdio = getEnvFlag3("FRONTMCP_STDIO");
|
|
66142
66855
|
const taskWorkerId = typeof process !== "undefined" ? process.env?.["FRONTMCP_RUN_TASK_ID"] : void 0;
|
|
66143
66856
|
if (taskWorkerId && metadata.serve !== false) {
|
|
66144
66857
|
void (async () => {
|
|
@@ -66164,7 +66877,7 @@ function FrontMcp(providedMetadata) {
|
|
|
66164
66877
|
} else if (isServerless2) {
|
|
66165
66878
|
const ServerlessInstance = getFrontMcpInstance();
|
|
66166
66879
|
const { setServerlessHandler: setServerlessHandler2, setServerlessHandlerPromise: setServerlessHandlerPromise2, setServerlessHandlerError: setServerlessHandlerError2 } = getServerlessHandlerFns();
|
|
66167
|
-
const isWorker =
|
|
66880
|
+
const isWorker = getEnvFlag3("FRONTMCP_WORKER");
|
|
66168
66881
|
const handlerPromise = isWorker ? ServerlessInstance.createFetchHandler(metadata) : ServerlessInstance.createHandler(metadata);
|
|
66169
66882
|
setServerlessHandlerPromise2(handlerPromise);
|
|
66170
66883
|
handlerPromise.then(setServerlessHandler2).catch((err) => {
|
|
@@ -66888,7 +67601,7 @@ var init_dynamic_adapter = __esm({
|
|
|
66888
67601
|
* @param options - Adapter options including required `name` field
|
|
66889
67602
|
* @throws Error if `name` is missing/empty or if a duplicate name is detected
|
|
66890
67603
|
*/
|
|
66891
|
-
static init(options) {
|
|
67604
|
+
static init(options = {}) {
|
|
66892
67605
|
const typedOptions = options;
|
|
66893
67606
|
const adapterName = typedOptions.name;
|
|
66894
67607
|
if (!adapterName || typeof adapterName !== "string" || adapterName.trim() === "") {
|
|
@@ -69065,6 +69778,7 @@ export {
|
|
|
69065
69778
|
resolveSchemaDescriptionFormat,
|
|
69066
69779
|
resolveToolVisibility,
|
|
69067
69780
|
resolveWithFallbacks,
|
|
69781
|
+
resolvesToVercelKvTaskBackend,
|
|
69068
69782
|
frontMcpResource as resource,
|
|
69069
69783
|
frontMcpResourceTemplate as resourceTemplate,
|
|
69070
69784
|
resourceUriMatches,
|