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