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