@frontmcp/sdk 1.8.3 → 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-execution-loop.d.ts.map +1 -1
- package/agent/agent.instance.d.ts +15 -4
- 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/auth.verify.flow.d.ts.map +1 -1
- package/auth/flows/oauth.auth-ui.flow.d.ts +1 -1
- package/auth/flows/oauth.authorize.flow.d.ts +23 -14
- package/auth/flows/oauth.authorize.flow.d.ts.map +1 -1
- package/auth/flows/oauth.callback.flow.d.ts +5 -2
- package/auth/flows/oauth.callback.flow.d.ts.map +1 -1
- 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.token.flow.d.ts.map +1 -1
- package/auth/flows/oauth.userinfo.flow.d.ts +1 -1
- package/auth/flows/oauth.userinfo.flow.d.ts.map +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 +48 -6
- package/auth/instances/instance.local-primary-auth.d.ts.map +1 -1
- package/auth/secure-store/secure-store.providers.d.ts +8 -5
- package/auth/secure-store/secure-store.providers.d.ts.map +1 -1
- package/auth/session-scope-identity.d.ts +18 -0
- package/auth/session-scope-identity.d.ts.map +1 -0
- package/auth/vault/auth-providers.providers.d.ts +2 -4
- package/auth/vault/auth-providers.providers.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/channel/sources/webhook.source.d.ts +1 -1
- package/channel/sources/webhook.source.d.ts.map +1 -1
- package/common/availability/availability.utils.d.ts +4 -2
- package/common/availability/availability.utils.d.ts.map +1 -1
- package/common/decorators/hook.decorator.d.ts +2 -0
- package/common/decorators/hook.decorator.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/agent.interface.d.ts +8 -0
- package/common/interfaces/agent.interface.d.ts.map +1 -1
- package/common/interfaces/channel.interface.d.ts +6 -0
- package/common/interfaces/channel.interface.d.ts.map +1 -1
- package/common/interfaces/execution-context.interface.d.ts +12 -0
- package/common/interfaces/execution-context.interface.d.ts.map +1 -1
- package/common/interfaces/internal/primary-auth-provider.interface.d.ts +5 -1
- package/common/interfaces/internal/primary-auth-provider.interface.d.ts.map +1 -1
- package/common/interfaces/job.interface.d.ts +3 -0
- package/common/interfaces/job.interface.d.ts.map +1 -1
- package/common/interfaces/server.interface.d.ts +2 -0
- package/common/interfaces/server.interface.d.ts.map +1 -1
- package/common/interfaces/tool.interface.d.ts.map +1 -1
- package/common/metadata/plugin.metadata.d.ts +9 -0
- package/common/metadata/plugin.metadata.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/tokens/plugin.tokens.d.ts +1 -0
- package/common/tokens/plugin.tokens.d.ts.map +1 -1
- package/common/utils/auth-info.utils.d.ts +26 -1
- package/common/utils/auth-info.utils.d.ts.map +1 -1
- package/common/utils/guard.utils.d.ts +2 -0
- package/common/utils/guard.utils.d.ts.map +1 -1
- package/common/utils/path.utils.d.ts +2 -0
- package/common/utils/path.utils.d.ts.map +1 -1
- package/context/call-surface.d.ts +6 -4
- package/context/call-surface.d.ts.map +1 -1
- package/context/frontmcp-context.d.ts +13 -1
- package/context/frontmcp-context.d.ts.map +1 -1
- package/direct/direct-server.d.ts.map +1 -1
- package/elicitation/helpers/elicit.helper.d.ts.map +1 -1
- package/elicitation/send-elicitation-result.tool.d.ts.map +1 -1
- package/errors/error-handler.d.ts.map +1 -1
- package/errors/index.d.ts +1 -0
- package/errors/index.d.ts.map +1 -1
- package/errors/mcp.error.d.ts.map +1 -1
- package/errors/session-identity-required.error.d.ts +20 -0
- package/errors/session-identity-required.error.d.ts.map +1 -0
- package/esm/index.mjs +1728 -488
- package/flows/flow.http-path.d.ts +21 -0
- package/flows/flow.http-path.d.ts.map +1 -0
- package/flows/flow.instance.d.ts.map +1 -1
- package/front-mcp/front-mcp.d.ts +5 -0
- package/front-mcp/front-mcp.d.ts.map +1 -1
- package/front-mcp/static-startup.check.d.ts +12 -0
- package/front-mcp/static-startup.check.d.ts.map +1 -0
- package/hooks/hook-coverage.d.ts +29 -0
- package/hooks/hook-coverage.d.ts.map +1 -0
- package/index.d.ts +15 -11
- package/index.d.ts.map +1 -1
- package/index.js +2222 -979
- package/job/execution/job-execution.manager.d.ts.map +1 -1
- package/job/job-scope.helper.d.ts.map +1 -1
- package/notification/notification.service.d.ts.map +1 -1
- package/package.json +10 -10
- package/prompt/flows/get-prompt.flow.d.ts.map +1 -1
- package/scope/flows/http.request.flow.d.ts +25 -1
- package/scope/flows/http.request.flow.d.ts.map +1 -1
- package/scope/scope.instance.d.ts +3 -3
- package/scope/scope.instance.d.ts.map +1 -1
- package/skill/auth/skill-http-auth.d.ts.map +1 -1
- package/skill/flows/http/llm-full-txt.flow.d.ts +1 -1
- package/skill/flows/http/llm-txt.flow.d.ts +1 -1
- package/skill/flows/http/skills-api.flow.d.ts +1 -1
- package/skill/flows/http/skills-api.flow.d.ts.map +1 -1
- package/skill/flows/load-skill.flow.d.ts.map +1 -1
- package/skill/index.d.ts +1 -1
- package/skill/index.d.ts.map +1 -1
- package/skill/sep-2640/sep-2640.per-skill.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 +31 -2
- package/skill/skill-filter.helper.d.ts.map +1 -1
- package/skill/skill-http.utils.d.ts.map +1 -1
- package/skill/skill-scope.helper.d.ts +11 -1
- package/skill/skill-scope.helper.d.ts.map +1 -1
- package/skill/skill.events.d.ts.map +1 -1
- package/skill/skill.registry.d.ts +121 -17
- package/skill/skill.registry.d.ts.map +1 -1
- package/task/helpers/task-runner.d.ts.map +1 -1
- package/task/runtime/execute-task.d.ts.map +1 -1
- package/task/task.types.d.ts +7 -0
- package/task/task.types.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/flows/handle.streamable-http.flow.d.ts.map +1 -1
- package/transport/in-memory-server.d.ts.map +1 -1
- package/transport/index.d.ts +2 -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/index.d.ts.map +1 -1
- package/transport/mcp-handlers/skills-load-request.handler.d.ts.map +1 -1
- package/transport/persistent-session-owner.d.ts +41 -0
- package/transport/persistent-session-owner.d.ts.map +1 -0
- package/transport/web-fetch-handler.d.ts +33 -0
- package/transport/web-fetch-handler.d.ts.map +1 -1
- package/transport/web-standard-mcp.d.ts +4 -0
- package/transport/web-standard-mcp.d.ts.map +1 -1
- package/workflow/engine/workflow-step.executor.d.ts.map +1 -1
- package/workflow/engine/workflow.engine.d.ts.map +1 -1
package/esm/index.mjs
CHANGED
|
@@ -110,7 +110,7 @@ function extractPublicMessageImpl(error, visited) {
|
|
|
110
110
|
}
|
|
111
111
|
return error == null ? "Unknown error" : String(error);
|
|
112
112
|
}
|
|
113
|
-
var MCP_ERROR_CODES, McpError, PublicMcpError, InternalMcpError, ToolNotFoundError, ToolNotConsentedError, EntryUnavailableError, ResourceNotFoundError, ResourceReadError, InvalidResourceUriError, InvalidInputError, InvalidOutputError, InvalidMethodError, PayloadTooLargeError, ToolExecutionError, RateLimitError, QuotaExceededError, UnauthorizedError, GenericServerError, DependencyNotFoundError, InvalidHookFlowError, InvalidPluginScopeError, RequestContextNotAvailableError, AuthConfigurationError, PromptNotFoundError, PromptExecutionError, SessionMissingError, UnsupportedClientVersionError, GlobalConfigNotFoundError, AuthorityDeniedMcpError, GuardLimitMcpError;
|
|
113
|
+
var MCP_ERROR_CODES, sequentialErrorIds, McpError, PublicMcpError, InternalMcpError, ToolNotFoundError, ToolNotConsentedError, EntryUnavailableError, ResourceNotFoundError, ResourceReadError, InvalidResourceUriError, InvalidInputError, InvalidOutputError, InvalidMethodError, PayloadTooLargeError, ToolExecutionError, RateLimitError, QuotaExceededError, UnauthorizedError, GenericServerError, DependencyNotFoundError, InvalidHookFlowError, InvalidPluginScopeError, RequestContextNotAvailableError, AuthConfigurationError, PromptNotFoundError, PromptExecutionError, SessionMissingError, UnsupportedClientVersionError, GlobalConfigNotFoundError, AuthorityDeniedMcpError, GuardLimitMcpError;
|
|
114
114
|
var init_mcp_error = __esm({
|
|
115
115
|
"libs/sdk/src/errors/mcp.error.ts"() {
|
|
116
116
|
"use strict";
|
|
@@ -132,6 +132,7 @@ var init_mcp_error = __esm({
|
|
|
132
132
|
/** Parse error (-32700) */
|
|
133
133
|
PARSE_ERROR: -32700
|
|
134
134
|
};
|
|
135
|
+
sequentialErrorIds = 0;
|
|
135
136
|
McpError = class extends Error {
|
|
136
137
|
/**
|
|
137
138
|
* Unique error ID for tracking in logs
|
|
@@ -144,7 +145,12 @@ var init_mcp_error = __esm({
|
|
|
144
145
|
Error.captureStackTrace(this, this.constructor);
|
|
145
146
|
}
|
|
146
147
|
generateErrorId() {
|
|
147
|
-
|
|
148
|
+
try {
|
|
149
|
+
return `err_${bytesToHex(randomBytes(8))}`;
|
|
150
|
+
} catch {
|
|
151
|
+
sequentialErrorIds += 1;
|
|
152
|
+
return `err_${sequentialErrorIds.toString(16).padStart(16, "0")}`;
|
|
153
|
+
}
|
|
148
154
|
}
|
|
149
155
|
/**
|
|
150
156
|
* Get the internal error message (for logging)
|
|
@@ -865,7 +871,8 @@ var init_plugin_tokens = __esm({
|
|
|
865
871
|
skills: tokenFactory.meta("skills"),
|
|
866
872
|
scope: tokenFactory.meta("scope"),
|
|
867
873
|
contextExtensions: tokenFactory.meta("contextExtensions"),
|
|
868
|
-
enforcesMetadata: tokenFactory.meta("enforcesMetadata")
|
|
874
|
+
enforcesMetadata: tokenFactory.meta("enforcesMetadata"),
|
|
875
|
+
dynamicSkills: tokenFactory.meta("dynamicSkills")
|
|
869
876
|
};
|
|
870
877
|
}
|
|
871
878
|
});
|
|
@@ -3436,7 +3443,8 @@ var init_plugin_metadata = __esm({
|
|
|
3436
3443
|
skills: z30.array(annotatedFrontMcpSkillsSchema).optional(),
|
|
3437
3444
|
scope: z30.enum(["app", "server"]).optional().default("app"),
|
|
3438
3445
|
contextExtensions: z30.array(contextExtensionSchema).optional(),
|
|
3439
|
-
enforcesMetadata: z30.array(z30.string().min(1)).optional()
|
|
3446
|
+
enforcesMetadata: z30.array(z30.string().min(1)).optional(),
|
|
3447
|
+
dynamicSkills: z30.boolean().optional()
|
|
3440
3448
|
}).passthrough();
|
|
3441
3449
|
}
|
|
3442
3450
|
});
|
|
@@ -4571,6 +4579,25 @@ var init_tool_credentials_required_error = __esm({
|
|
|
4571
4579
|
}
|
|
4572
4580
|
});
|
|
4573
4581
|
|
|
4582
|
+
// libs/sdk/src/errors/session-identity-required.error.ts
|
|
4583
|
+
var SessionIdentityRequiredError;
|
|
4584
|
+
var init_session_identity_required_error = __esm({
|
|
4585
|
+
"libs/sdk/src/errors/session-identity-required.error.ts"() {
|
|
4586
|
+
"use strict";
|
|
4587
|
+
init_mcp_error();
|
|
4588
|
+
SessionIdentityRequiredError = class extends PublicMcpError {
|
|
4589
|
+
mcpErrorCode = MCP_ERROR_CODES.FORBIDDEN;
|
|
4590
|
+
constructor(what = "Session-scoped storage") {
|
|
4591
|
+
super(
|
|
4592
|
+
`${what} needs a verified session or a signed-in caller. Authenticate the request, or use a session transport (an MCP revision before 2026-07-28).`,
|
|
4593
|
+
"SESSION_IDENTITY_REQUIRED",
|
|
4594
|
+
403
|
|
4595
|
+
);
|
|
4596
|
+
}
|
|
4597
|
+
};
|
|
4598
|
+
}
|
|
4599
|
+
});
|
|
4600
|
+
|
|
4574
4601
|
// libs/sdk/src/errors/error-handler.ts
|
|
4575
4602
|
import { isProduction as isProduction2 } from "@frontmcp/utils";
|
|
4576
4603
|
function createErrorHandler(options = {}) {
|
|
@@ -5932,6 +5959,7 @@ __export(errors_exports, {
|
|
|
5932
5959
|
ServerlessHandlerNotInitializedError: () => ServerlessHandlerNotInitializedError,
|
|
5933
5960
|
SessionClaimConflictError: () => SessionClaimConflictError,
|
|
5934
5961
|
SessionIdEmptyError: () => SessionIdEmptyError,
|
|
5962
|
+
SessionIdentityRequiredError: () => SessionIdentityRequiredError,
|
|
5935
5963
|
SessionMissingError: () => SessionMissingError,
|
|
5936
5964
|
SessionSecretRequiredError: () => SessionSecretRequiredError,
|
|
5937
5965
|
SessionVerificationFailedError: () => SessionVerificationFailedError,
|
|
@@ -5986,6 +6014,7 @@ var init_errors = __esm({
|
|
|
5986
6014
|
init_mcp_error();
|
|
5987
6015
|
init_authorization_required_error();
|
|
5988
6016
|
init_tool_credentials_required_error();
|
|
6017
|
+
init_session_identity_required_error();
|
|
5989
6018
|
init_error_handler();
|
|
5990
6019
|
init_agent_errors();
|
|
5991
6020
|
init_task_error();
|
|
@@ -6468,30 +6497,30 @@ var require_dist = __commonJS({
|
|
|
6468
6497
|
sqliteStorageOptionsSchema: () => sqliteStorageOptionsSchema
|
|
6469
6498
|
});
|
|
6470
6499
|
module.exports = __toCommonJS2(index_exports);
|
|
6471
|
-
var
|
|
6500
|
+
var import_utils114 = __require("@frontmcp/utils");
|
|
6472
6501
|
var HKDF_SALT = new TextEncoder().encode("frontmcp-sqlite-storage-v1");
|
|
6473
6502
|
var HKDF_INFO = new TextEncoder().encode("aes-256-gcm-value-encryption");
|
|
6474
6503
|
var KEY_LENGTH = 32;
|
|
6475
6504
|
function deriveEncryptionKey(secret) {
|
|
6476
6505
|
const ikm = new TextEncoder().encode(secret);
|
|
6477
|
-
return (0,
|
|
6506
|
+
return (0, import_utils114.hkdfSha256)(ikm, HKDF_SALT, HKDF_INFO, KEY_LENGTH);
|
|
6478
6507
|
}
|
|
6479
6508
|
var SEPARATOR = ":";
|
|
6480
6509
|
function encryptValue(key, plaintext) {
|
|
6481
|
-
const iv = (0,
|
|
6510
|
+
const iv = (0, import_utils114.randomBytes)(12);
|
|
6482
6511
|
const plaintextBytes = new TextEncoder().encode(plaintext);
|
|
6483
|
-
const { ciphertext, tag } = (0,
|
|
6484
|
-
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);
|
|
6485
6514
|
}
|
|
6486
6515
|
function decryptValue(key, encrypted) {
|
|
6487
6516
|
const parts = encrypted.split(SEPARATOR);
|
|
6488
6517
|
if (parts.length !== 3) {
|
|
6489
6518
|
throw new Error("Invalid encrypted value format");
|
|
6490
6519
|
}
|
|
6491
|
-
const iv = (0,
|
|
6492
|
-
const tag = (0,
|
|
6493
|
-
const ciphertext = (0,
|
|
6494
|
-
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);
|
|
6495
6524
|
return new TextDecoder().decode(plaintext);
|
|
6496
6525
|
}
|
|
6497
6526
|
var import_utils210 = __require("@frontmcp/utils");
|
|
@@ -7405,8 +7434,8 @@ function detectStorageType(storage, configuredType) {
|
|
|
7405
7434
|
return "memory";
|
|
7406
7435
|
}
|
|
7407
7436
|
async function createTaskStore(options = {}) {
|
|
7408
|
-
const { storage: storageConfig, redis, keyPrefix = "mcp:task:", logger, isEdgeRuntime:
|
|
7409
|
-
if (
|
|
7437
|
+
const { storage: storageConfig, redis, keyPrefix = "mcp:task:", logger, isEdgeRuntime: isEdgeRuntime3 = false, sqlite } = options;
|
|
7438
|
+
if (isEdgeRuntime3 && sqlite) {
|
|
7410
7439
|
throw new TaskStoreNotSupportedError(
|
|
7411
7440
|
"SQLite task store is not supported on Edge runtime (better-sqlite3 is a native module). Use Redis or Upstash."
|
|
7412
7441
|
);
|
|
@@ -7461,7 +7490,7 @@ async function createTaskStore(options = {}) {
|
|
|
7461
7490
|
"Vercel KV is not supported for task stores (pub/sub required). Use Redis or Upstash."
|
|
7462
7491
|
);
|
|
7463
7492
|
}
|
|
7464
|
-
if (
|
|
7493
|
+
if (isEdgeRuntime3 && (!finalStorageConfig || finalStorageConfig.type === "memory")) {
|
|
7465
7494
|
throw new TaskStoreNotSupportedError(
|
|
7466
7495
|
"Tasks require distributed storage on Edge runtime. Configure Redis or Upstash."
|
|
7467
7496
|
);
|
|
@@ -7469,7 +7498,7 @@ async function createTaskStore(options = {}) {
|
|
|
7469
7498
|
const storage = await createStorage({
|
|
7470
7499
|
...finalStorageConfig,
|
|
7471
7500
|
prefix: keyPrefix,
|
|
7472
|
-
fallback:
|
|
7501
|
+
fallback: isEdgeRuntime3 ? "error" : "memory"
|
|
7473
7502
|
});
|
|
7474
7503
|
const store = new StorageTaskStore(storage, logger);
|
|
7475
7504
|
const type = detectStorageType(storage, finalStorageConfig?.type);
|
|
@@ -7651,8 +7680,9 @@ var init_task_id = __esm({
|
|
|
7651
7680
|
});
|
|
7652
7681
|
|
|
7653
7682
|
// libs/sdk/src/task/helpers/task-runner.ts
|
|
7683
|
+
import { runRequestExclusive } from "@frontmcp/utils";
|
|
7654
7684
|
function runTaskInBackground(params) {
|
|
7655
|
-
return Promise.resolve().then(() => executeTask(params));
|
|
7685
|
+
return Promise.resolve().then(() => runRequestExclusive(() => executeTask(params)));
|
|
7656
7686
|
}
|
|
7657
7687
|
async function executeTask(params) {
|
|
7658
7688
|
const { record, cleanedRequestParams, ctx, scope, store, registry, notifier, logger } = params;
|
|
@@ -8694,6 +8724,7 @@ var init_get_prompt_request_handler = __esm({
|
|
|
8694
8724
|
import {
|
|
8695
8725
|
ListRootsResultSchema
|
|
8696
8726
|
} from "@frontmcp/protocol";
|
|
8727
|
+
import { awaitOutsideRequest } from "@frontmcp/utils";
|
|
8697
8728
|
function hasMcpAppsExtension(capabilities) {
|
|
8698
8729
|
if (!capabilities?.experimental) {
|
|
8699
8730
|
return false;
|
|
@@ -9410,9 +9441,11 @@ var init_notification_service = __esm({
|
|
|
9410
9441
|
}
|
|
9411
9442
|
try {
|
|
9412
9443
|
this.logger.verbose(`Requesting roots from client for session ${sessionId.slice(0, 20)}...`);
|
|
9413
|
-
const result = await
|
|
9414
|
-
|
|
9415
|
-
|
|
9444
|
+
const result = await awaitOutsideRequest(
|
|
9445
|
+
registered.server.request({ method: "roots/list" }, ListRootsResultSchema, {
|
|
9446
|
+
timeout: options?.timeout ?? 3e4
|
|
9447
|
+
})
|
|
9448
|
+
);
|
|
9416
9449
|
registered.cachedRoots = result.roots;
|
|
9417
9450
|
registered.rootsFetchedAt = Date.now();
|
|
9418
9451
|
this.logger.verbose(
|
|
@@ -10221,7 +10254,7 @@ function callerSurface(ctx, surface) {
|
|
|
10221
10254
|
}
|
|
10222
10255
|
async function filterServableSkills(scope, skills, ctx, surface) {
|
|
10223
10256
|
const caller = callerSurface(ctx, surface);
|
|
10224
|
-
const offered = skills.filter((skill) =>
|
|
10257
|
+
const offered = skills.filter((skill) => availabilityForCall(skill.metadata.availableWhen, caller) === "available");
|
|
10225
10258
|
if (offered.length === 0) return [];
|
|
10226
10259
|
const { skills: servable } = await scope.runFlowForOutput("skills:filter", { skills: [...offered], ctx });
|
|
10227
10260
|
const servableSkills = new Set(servable);
|
|
@@ -10242,7 +10275,33 @@ async function filterDiscoverableSkillResults(scope, registry, results, caller =
|
|
|
10242
10275
|
return entry === void 0 || servable.has(entry);
|
|
10243
10276
|
});
|
|
10244
10277
|
}
|
|
10245
|
-
|
|
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;
|
|
10246
10305
|
var init_skill_filter_helper = __esm({
|
|
10247
10306
|
"libs/sdk/src/skill/skill-filter.helper.ts"() {
|
|
10248
10307
|
"use strict";
|
|
@@ -10251,6 +10310,9 @@ var init_skill_filter_helper = __esm({
|
|
|
10251
10310
|
init_skill_authorities_helper();
|
|
10252
10311
|
init_skill_entry_resolver();
|
|
10253
10312
|
SKILLS_HTTP_SURFACE = "mcp";
|
|
10313
|
+
WARNING_PREFIX = /^Skill "(.*)" references /;
|
|
10314
|
+
WARNING_SUFFIX = ". Some functionality may be limited.";
|
|
10315
|
+
MISSING_PART = "missing tools: ";
|
|
10254
10316
|
}
|
|
10255
10317
|
});
|
|
10256
10318
|
|
|
@@ -10351,7 +10413,13 @@ async function formatSkillsForLlmFull(registry, toolRegistry, visibility = "both
|
|
|
10351
10413
|
}
|
|
10352
10414
|
const loaded = await registry.loadSkill(skill.name);
|
|
10353
10415
|
if (loaded) {
|
|
10354
|
-
|
|
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));
|
|
10355
10423
|
}
|
|
10356
10424
|
}
|
|
10357
10425
|
return parts.join("\n\n---\n\n");
|
|
@@ -10471,6 +10539,7 @@ function skillToApiResponse(skill, loadResult) {
|
|
|
10471
10539
|
var init_skill_http_utils = __esm({
|
|
10472
10540
|
"libs/sdk/src/skill/skill-http.utils.ts"() {
|
|
10473
10541
|
"use strict";
|
|
10542
|
+
init_skill_filter_helper();
|
|
10474
10543
|
}
|
|
10475
10544
|
});
|
|
10476
10545
|
|
|
@@ -11032,7 +11101,11 @@ function skillsLoadRequestHandler({
|
|
|
11032
11101
|
throw toSdkMcpError(err);
|
|
11033
11102
|
}
|
|
11034
11103
|
}
|
|
11035
|
-
const { skill, availableTools, missingTools, isComplete, warning } =
|
|
11104
|
+
const { skill, availableTools, missingTools, isComplete, warning } = skillToolsForCaller(
|
|
11105
|
+
loadResult,
|
|
11106
|
+
toolRegistry,
|
|
11107
|
+
withMcpSurface(scope, ctx)
|
|
11108
|
+
);
|
|
11036
11109
|
if (warning) {
|
|
11037
11110
|
warnings.push(warning);
|
|
11038
11111
|
}
|
|
@@ -11889,7 +11962,8 @@ function createMcpHandlers(options) {
|
|
|
11889
11962
|
const hasPromptsOrResources = options.scope?.prompts?.hasAny() || options.scope?.resources?.hasAny();
|
|
11890
11963
|
const completionHandler = hasPromptsOrResources && options.serverOptions?.capabilities?.completions ? [completeRequestHandler(options)] : [];
|
|
11891
11964
|
const loggingHandler = options.serverOptions?.capabilities?.logging ? [LoggingSetLevelRequestHandler(options)] : [];
|
|
11892
|
-
const
|
|
11965
|
+
const skillRegistry = options.scope?.skills;
|
|
11966
|
+
const skillsHandler = skillRegistry?.servesSkills?.() ?? skillRegistry?.hasAny() ? [skillsSearchRequestHandler(options), skillsLoadRequestHandler(options), skillsListRequestHandler(options)] : [];
|
|
11893
11967
|
const tasksHandler = options.serverOptions?.capabilities?.["tasks"] ? [
|
|
11894
11968
|
tasksGetRequestHandler(options),
|
|
11895
11969
|
tasksResultRequestHandler(options),
|
|
@@ -11944,7 +12018,7 @@ var in_memory_server_exports = {};
|
|
|
11944
12018
|
__export(in_memory_server_exports, {
|
|
11945
12019
|
createInMemoryServer: () => createInMemoryServer
|
|
11946
12020
|
});
|
|
11947
|
-
import { randomUUID as randomUUID4 } from "@frontmcp/utils";
|
|
12021
|
+
import { randomUUID as randomUUID4, runRequestExclusive as runRequestExclusive2 } from "@frontmcp/utils";
|
|
11948
12022
|
async function createInMemoryServer(scope, options) {
|
|
11949
12023
|
const { InMemoryTransport, McpServer: McpServer2 } = await import("@frontmcp/protocol");
|
|
11950
12024
|
const { createMcpHandlers: createMcpHandlers2 } = await Promise.resolve().then(() => (init_mcp_handlers(), mcp_handlers_exports));
|
|
@@ -11964,7 +12038,7 @@ async function createInMemoryServer(scope, options) {
|
|
|
11964
12038
|
sessionId
|
|
11965
12039
|
}
|
|
11966
12040
|
};
|
|
11967
|
-
return originalHandler(request, enrichedCtx);
|
|
12041
|
+
return runRequestExclusive2(() => originalHandler(request, enrichedCtx));
|
|
11968
12042
|
};
|
|
11969
12043
|
mcpServer.setRequestHandler(handler.requestSchema, wrappedHandler);
|
|
11970
12044
|
}
|
|
@@ -12612,7 +12686,7 @@ var init_connect = __esm({
|
|
|
12612
12686
|
});
|
|
12613
12687
|
|
|
12614
12688
|
// libs/sdk/src/direct/direct-server.ts
|
|
12615
|
-
import { randomUUID as randomUUID6, sha256Hex } from "@frontmcp/utils";
|
|
12689
|
+
import { randomUUID as randomUUID6, runRequestExclusive as runRequestExclusive3, sha256Hex } from "@frontmcp/utils";
|
|
12616
12690
|
function buildAuthInfo(authContext, defaultSessionId) {
|
|
12617
12691
|
if (!authContext && !defaultSessionId) {
|
|
12618
12692
|
return void 0;
|
|
@@ -12687,7 +12761,7 @@ var init_direct_server = __esm({
|
|
|
12687
12761
|
}
|
|
12688
12762
|
const ctx = this.buildHandlerContext(options);
|
|
12689
12763
|
try {
|
|
12690
|
-
return await this.scope.runFlowForOutput(flowName, { request, ctx });
|
|
12764
|
+
return await runRequestExclusive3(() => this.scope.runFlowForOutput(flowName, { request, ctx }));
|
|
12691
12765
|
} catch (e) {
|
|
12692
12766
|
if (e instanceof FlowControl && e.type === "respond") {
|
|
12693
12767
|
return e.output;
|
|
@@ -14116,11 +14190,19 @@ var init_frontmcp_context = __esm({
|
|
|
14116
14190
|
updateAuthInfo(authInfo) {
|
|
14117
14191
|
this._authInfo = { ...this._authInfo, ...authInfo };
|
|
14118
14192
|
}
|
|
14119
|
-
/**
|
|
14193
|
+
/**
|
|
14194
|
+
* The session the server verified for this request; undefined for a request without one (MCP
|
|
14195
|
+
* 2026-07-28, the stateless transports, an `mcp-session-id` the server did not accept).
|
|
14196
|
+
*
|
|
14197
|
+
* Over HTTP that is the session session verification recorded (`authInfo.extra.sessionId`),
|
|
14198
|
+
* which it records only for a session the request presented: its `mcp-session-id` header, or the
|
|
14199
|
+
* `?sessionId=` a legacy SSE client posts its messages with. Over an in-process transport (stdio,
|
|
14200
|
+
* `connect()`, `createDirect()`) it is the session the transport runs the request in.
|
|
14201
|
+
*/
|
|
14120
14202
|
get verifiedSessionId() {
|
|
14121
|
-
|
|
14122
|
-
const verified = this._authInfo.sessionId
|
|
14123
|
-
return verified ===
|
|
14203
|
+
const recorded = this._authInfo.extra?.["sessionId"];
|
|
14204
|
+
const verified = typeof recorded === "string" && recorded.length > 0 ? recorded : this._authInfo.sessionId === this.sessionId ? this.sessionId : void 0;
|
|
14205
|
+
return verified === STATELESS_SESSION_ID ? void 0 : verified;
|
|
14124
14206
|
}
|
|
14125
14207
|
/**
|
|
14126
14208
|
* The client's self-reported identity, sent with this request (MCP 2026-07-28 `_meta`).
|
|
@@ -14572,6 +14654,9 @@ function readPublicUrlPin() {
|
|
|
14572
14654
|
function isPublicUrlPinned() {
|
|
14573
14655
|
return readPublicUrlPin() !== void 0;
|
|
14574
14656
|
}
|
|
14657
|
+
function getPinnedPublicUrl() {
|
|
14658
|
+
return readPublicUrlPin();
|
|
14659
|
+
}
|
|
14575
14660
|
function isProxyTrusted() {
|
|
14576
14661
|
try {
|
|
14577
14662
|
if (typeof process !== "undefined" && process.env) {
|
|
@@ -15863,6 +15948,9 @@ function registerHookOnClass(ctor, meta) {
|
|
|
15863
15948
|
function resolvePendingTC39HooksForClass(ctor) {
|
|
15864
15949
|
return pendingHookRegistry.resolveForClass(ctor);
|
|
15865
15950
|
}
|
|
15951
|
+
function peekPendingTC39HooksForClass(ctor) {
|
|
15952
|
+
return pendingHookRegistry.resolveForClass(ctor, false);
|
|
15953
|
+
}
|
|
15866
15954
|
function registerPendingTC39Hook(method, meta) {
|
|
15867
15955
|
pendingHookRegistry.store(method, meta);
|
|
15868
15956
|
}
|
|
@@ -16008,7 +16096,7 @@ var init_elicitation_types = __esm({
|
|
|
16008
16096
|
|
|
16009
16097
|
// libs/sdk/src/elicitation/helpers/elicit.helper.ts
|
|
16010
16098
|
import { toJSONSchema, z as z45 } from "@frontmcp/lazy-zod";
|
|
16011
|
-
import { randomUUID as randomUUID10 } from "@frontmcp/utils";
|
|
16099
|
+
import { awaitOutsideRequest as awaitOutsideRequest2, randomUUID as randomUUID10 } from "@frontmcp/utils";
|
|
16012
16100
|
async function performElicit(deps, message, requestedSchema, options) {
|
|
16013
16101
|
const { sessionId, getClientCapabilities, tryGetContext, entryName, entryInput, elicitationEnabled } = deps;
|
|
16014
16102
|
if (elicitationEnabled !== true) {
|
|
@@ -16016,6 +16104,11 @@ async function performElicit(deps, message, requestedSchema, options) {
|
|
|
16016
16104
|
}
|
|
16017
16105
|
const ctx = tryGetContext();
|
|
16018
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
|
+
}
|
|
16019
16112
|
if (mrtr) {
|
|
16020
16113
|
const zodSchema = requestedSchema instanceof z45.ZodType ? requestedSchema : z45.object(requestedSchema);
|
|
16021
16114
|
const answer = mrtr.resolveElicitation({
|
|
@@ -16056,7 +16149,7 @@ async function performElicit(deps, message, requestedSchema, options) {
|
|
|
16056
16149
|
if (!transport) {
|
|
16057
16150
|
throw new ElicitationNotSupportedError("Transport not available for elicitation");
|
|
16058
16151
|
}
|
|
16059
|
-
return transport.elicit(message, requestedSchema, options);
|
|
16152
|
+
return awaitOutsideRequest2(transport.elicit(message, requestedSchema, options));
|
|
16060
16153
|
}
|
|
16061
16154
|
function generateElicitationId() {
|
|
16062
16155
|
return `elicit-${randomUUID10()}`;
|
|
@@ -18224,12 +18317,12 @@ function qualifiedNameOf(lineage, name44) {
|
|
|
18224
18317
|
return `${ownerKeyOf(lineage)}:${name44}`;
|
|
18225
18318
|
}
|
|
18226
18319
|
function hookOwnerIdOf(lineage, owner) {
|
|
18227
|
-
const
|
|
18228
|
-
return appOwnerIdOf(
|
|
18320
|
+
const owners2 = owner ? [...lineage, owner] : lineage;
|
|
18321
|
+
return appOwnerIdOf(owners2) ?? owners2[owners2.length - 1]?.id;
|
|
18229
18322
|
}
|
|
18230
18323
|
function appOwnerIdOf(lineage, owner) {
|
|
18231
|
-
const
|
|
18232
|
-
return
|
|
18324
|
+
const owners2 = owner ? [...lineage, owner] : lineage;
|
|
18325
|
+
return owners2.find((candidate) => candidate.kind === "app")?.id;
|
|
18233
18326
|
}
|
|
18234
18327
|
var EntryLineageIndex;
|
|
18235
18328
|
var init_lineage_utils = __esm({
|
|
@@ -18327,6 +18420,7 @@ var init_get_prompt_flow = __esm({
|
|
|
18327
18420
|
init_remote_capabilities_utils();
|
|
18328
18421
|
init_common();
|
|
18329
18422
|
init_availability();
|
|
18423
|
+
init_call_surface();
|
|
18330
18424
|
init_errors();
|
|
18331
18425
|
init_resolved_entries();
|
|
18332
18426
|
init_hooks_utils();
|
|
@@ -18530,7 +18624,10 @@ var init_get_prompt_flow = __esm({
|
|
|
18530
18624
|
}
|
|
18531
18625
|
promptContext.mark("execute");
|
|
18532
18626
|
try {
|
|
18533
|
-
promptContext.output = await
|
|
18627
|
+
promptContext.output = await runOnSurface(
|
|
18628
|
+
callSurfaceOf(this.input.ctx),
|
|
18629
|
+
async () => promptContext.execute(parsedArgs)
|
|
18630
|
+
);
|
|
18534
18631
|
this.logger.verbose("execute:done");
|
|
18535
18632
|
} catch (error) {
|
|
18536
18633
|
if (error instanceof FlowControl || isClientFacingError(error)) throw error;
|
|
@@ -21615,6 +21712,7 @@ var init_send_elicitation_result_tool = __esm({
|
|
|
21615
21712
|
"libs/sdk/src/elicitation/send-elicitation-result.tool.ts"() {
|
|
21616
21713
|
"use strict";
|
|
21617
21714
|
init_common();
|
|
21715
|
+
init_call_surface();
|
|
21618
21716
|
init_errors();
|
|
21619
21717
|
init_fallback_helper();
|
|
21620
21718
|
inputSchema12 = {
|
|
@@ -21695,8 +21793,15 @@ var init_send_elicitation_result_tool = __esm({
|
|
|
21695
21793
|
arguments: pending.toolInput
|
|
21696
21794
|
}
|
|
21697
21795
|
},
|
|
21698
|
-
//
|
|
21699
|
-
|
|
21796
|
+
// The call context of this call, whose caller is the owner: its auth info as its transport
|
|
21797
|
+
// gave it (the tool reads it as `this.auth`), on the surface it was made from. Not the
|
|
21798
|
+
// request context object, whose auth info is a getter a call context does not carry over.
|
|
21799
|
+
ctx: {
|
|
21800
|
+
authInfo: this.authInfo,
|
|
21801
|
+
requestId: this.tryGetContext()?.requestId ?? this.runId,
|
|
21802
|
+
surface: getCallSurface(),
|
|
21803
|
+
...this.signal ? { signal: this.signal } : {}
|
|
21804
|
+
}
|
|
21700
21805
|
});
|
|
21701
21806
|
this.logger.info("sendElicitationResult: original tool completed", {
|
|
21702
21807
|
elicitId,
|
|
@@ -21874,7 +21979,14 @@ import {
|
|
|
21874
21979
|
MCP_APPS_MIME_TYPE as MCP_APPS_MIME_TYPE2,
|
|
21875
21980
|
resolveServingMode
|
|
21876
21981
|
} from "@frontmcp/uipack/adapters";
|
|
21877
|
-
import {
|
|
21982
|
+
import {
|
|
21983
|
+
awaitOutsideRequest as awaitOutsideRequest3,
|
|
21984
|
+
findNonFiniteNumber,
|
|
21985
|
+
getRuntimeContext as getRuntimeContext7,
|
|
21986
|
+
isDebug,
|
|
21987
|
+
isDevelopment as isDevelopment2,
|
|
21988
|
+
randomUUID as randomUUID11
|
|
21989
|
+
} from "@frontmcp/utils";
|
|
21878
21990
|
function toolNameCandidates(name44) {
|
|
21879
21991
|
if (!/[-_]/.test(name44)) return [name44];
|
|
21880
21992
|
return [name44, name44.includes("_") ? name44.replace(/_/g, "-") : name44.replace(/-/g, "_")];
|
|
@@ -22138,6 +22250,8 @@ var init_call_tool_flow = __esm({
|
|
|
22138
22250
|
request: { method: "tools/call", params: cleanedParams },
|
|
22139
22251
|
scopeId: this.scope.id
|
|
22140
22252
|
};
|
|
22253
|
+
const callSurface = callSurfaceOf(this.input.ctx);
|
|
22254
|
+
if (callSurface !== void 0) record.surface = callSurface;
|
|
22141
22255
|
if (this.state.progressToken !== void 0) {
|
|
22142
22256
|
record.progressToken = this.state.progressToken;
|
|
22143
22257
|
}
|
|
@@ -22362,6 +22476,7 @@ var init_call_tool_flow = __esm({
|
|
|
22362
22476
|
context.mark("createToolCallContext");
|
|
22363
22477
|
context._toolNameInternal = tool.metadata.id ?? tool.metadata.name;
|
|
22364
22478
|
context._toolInputInternal = input.arguments;
|
|
22479
|
+
context._jsonRpcRequestIdInternal = this.state.jsonRpcRequestId;
|
|
22365
22480
|
const frontmcpContext = context.tryGetContext();
|
|
22366
22481
|
const authInfoWithTransport = authInfo;
|
|
22367
22482
|
if (frontmcpContext && authInfoWithTransport?.transport?.sendElicitRequest) {
|
|
@@ -22543,8 +22658,11 @@ var init_call_tool_flow = __esm({
|
|
|
22543
22658
|
elicitId: error.elicitId,
|
|
22544
22659
|
sessionId,
|
|
22545
22660
|
owner,
|
|
22546
|
-
|
|
22547
|
-
|
|
22661
|
+
// The answer re-runs the call this flow ran: this tool, with these arguments. What asked
|
|
22662
|
+
// may be an entry the tool runs, like the agent behind `invoke_<agent>`, whose own name
|
|
22663
|
+
// no `tools/call` resolves.
|
|
22664
|
+
toolName: tool.fullName,
|
|
22665
|
+
toolInput: this.state.required.input.arguments,
|
|
22548
22666
|
elicitMessage: error.elicitMessage,
|
|
22549
22667
|
elicitSchema: error.schema,
|
|
22550
22668
|
createdAt: Date.now(),
|
|
@@ -22556,7 +22674,7 @@ var init_call_tool_flow = __esm({
|
|
|
22556
22674
|
elicitId: error.elicitId
|
|
22557
22675
|
});
|
|
22558
22676
|
const deps = { scope: this.scope, sessionId, logger: this.logger };
|
|
22559
|
-
const result = await handleWaitingFallback(deps, error);
|
|
22677
|
+
const result = await awaitOutsideRequest3(handleWaitingFallback(deps, error));
|
|
22560
22678
|
toolContext.output = result;
|
|
22561
22679
|
this.logger.verbose("execute:done (elicitation waiting fallback)");
|
|
22562
22680
|
return;
|
|
@@ -24430,6 +24548,39 @@ var init_server_validation = __esm({
|
|
|
24430
24548
|
}
|
|
24431
24549
|
});
|
|
24432
24550
|
|
|
24551
|
+
// libs/sdk/src/flows/flow.http-path.ts
|
|
24552
|
+
function matchMountedPath(pattern, path3) {
|
|
24553
|
+
if (!pattern.includes("/:")) {
|
|
24554
|
+
const prefix = pattern.endsWith("/") ? pattern : `${pattern}/`;
|
|
24555
|
+
return path3 === pattern || path3.startsWith(prefix) ? {} : void 0;
|
|
24556
|
+
}
|
|
24557
|
+
const mount = pattern.endsWith("/") ? pattern.slice(0, -1) : pattern;
|
|
24558
|
+
const expected = mount.split("/");
|
|
24559
|
+
const actual = path3.split("/");
|
|
24560
|
+
if (actual.length < expected.length) return void 0;
|
|
24561
|
+
const params = {};
|
|
24562
|
+
for (let i = 0; i < expected.length; i++) {
|
|
24563
|
+
const segment = expected[i];
|
|
24564
|
+
const value = actual[i];
|
|
24565
|
+
if (segment.length > 1 && segment.startsWith(":")) {
|
|
24566
|
+
if (value.length === 0) return void 0;
|
|
24567
|
+
try {
|
|
24568
|
+
params[segment.slice(1)] = decodeURIComponent(value);
|
|
24569
|
+
} catch {
|
|
24570
|
+
return void 0;
|
|
24571
|
+
}
|
|
24572
|
+
} else if (segment !== value) {
|
|
24573
|
+
return void 0;
|
|
24574
|
+
}
|
|
24575
|
+
}
|
|
24576
|
+
return params;
|
|
24577
|
+
}
|
|
24578
|
+
var init_flow_http_path = __esm({
|
|
24579
|
+
"libs/sdk/src/flows/flow.http-path.ts"() {
|
|
24580
|
+
"use strict";
|
|
24581
|
+
}
|
|
24582
|
+
});
|
|
24583
|
+
|
|
24433
24584
|
// libs/sdk/src/flows/flow.stages.ts
|
|
24434
24585
|
function sortStageMap(table) {
|
|
24435
24586
|
for (const key of Object.keys(table)) {
|
|
@@ -24537,6 +24688,7 @@ var init_flow_instance = __esm({
|
|
|
24537
24688
|
init_context();
|
|
24538
24689
|
init_errors();
|
|
24539
24690
|
init_server_validation();
|
|
24691
|
+
init_flow_http_path();
|
|
24540
24692
|
init_flow_stages();
|
|
24541
24693
|
FAILED_OUTCOMES = /* @__PURE__ */ new Set(["fail", "abort", "unknown_error"]);
|
|
24542
24694
|
CAP = (s) => s ? s[0].toUpperCase() + s.slice(1) : s;
|
|
@@ -24656,12 +24808,16 @@ var init_flow_instance = __esm({
|
|
|
24656
24808
|
const hasMwCanActivate = (mw.canActivate?.length ?? 0) > 0;
|
|
24657
24809
|
if (!path3 && !hasStaticCanActivate && !hasMwCanActivate) return false;
|
|
24658
24810
|
if (this.method && request.method !== this.method) return false;
|
|
24659
|
-
|
|
24660
|
-
|
|
24661
|
-
|
|
24662
|
-
|
|
24811
|
+
const captured = path3 ? matchMountedPath(path3, request.path) : {};
|
|
24812
|
+
if (!captured) return false;
|
|
24813
|
+
const previous = request.params;
|
|
24814
|
+
if (Object.keys(captured).length > 0) request.params = { ...captured, ...previous };
|
|
24815
|
+
const activated = await this.canActivate(request);
|
|
24816
|
+
if (!activated) {
|
|
24817
|
+
if (previous === void 0) delete request.params;
|
|
24818
|
+
else request.params = previous;
|
|
24663
24819
|
}
|
|
24664
|
-
return
|
|
24820
|
+
return activated;
|
|
24665
24821
|
}
|
|
24666
24822
|
/**
|
|
24667
24823
|
* Get FrontMcpContextStorage from providers (with fallback).
|
|
@@ -25656,6 +25812,11 @@ var init_execution_context_interface = __esm({
|
|
|
25656
25812
|
* Authority/authorization checks still run — an internal call carries the
|
|
25657
25813
|
* same `authInfo` as the surrounding request, so ABAC/RBAC guards apply.
|
|
25658
25814
|
*
|
|
25815
|
+
* `availableWhen.surface`: the call carries {@link callToolSurface}. From a tool, resource or
|
|
25816
|
+
* prompt that is none (in-process dispatch, which no surface restricts); a job's call carries
|
|
25817
|
+
* `'job'`, an agent's `'agent'`, and an HTTP trigger's `'http-trigger'`, so a tool whose `surface`
|
|
25818
|
+
* leaves that caller out answers as an unknown tool.
|
|
25819
|
+
*
|
|
25659
25820
|
* @param name Tool name (or fully-qualified `owner.name`).
|
|
25660
25821
|
* @param args Tool arguments — validated by the tool's input schema.
|
|
25661
25822
|
* @param opts Optional progress token / abort signal forwarded into `_meta`.
|
|
@@ -25673,10 +25834,12 @@ var init_execution_context_interface = __esm({
|
|
|
25673
25834
|
...Object.keys(requestMeta).length > 0 && { _meta: requestMeta }
|
|
25674
25835
|
}
|
|
25675
25836
|
};
|
|
25837
|
+
const surface = this.callToolSurface();
|
|
25676
25838
|
const ctx = {
|
|
25677
25839
|
authInfo: this.getAuthInfo(),
|
|
25678
25840
|
requestId: this.tryGetContext()?.requestId ?? this.runId,
|
|
25679
25841
|
internalCall: true,
|
|
25842
|
+
...surface && { surface },
|
|
25680
25843
|
...opts?.signal && { signal: opts.signal }
|
|
25681
25844
|
};
|
|
25682
25845
|
const result = await scope.runFlow("tools:call-tool", { request, ctx });
|
|
@@ -25685,6 +25848,14 @@ var init_execution_context_interface = __esm({
|
|
|
25685
25848
|
}
|
|
25686
25849
|
return result;
|
|
25687
25850
|
}
|
|
25851
|
+
/**
|
|
25852
|
+
* The surface (`availableWhen.surface`) a {@link callTool} from this context calls on. None by
|
|
25853
|
+
* default: a tool, resource or prompt composing with other tools is in-process dispatch. Contexts
|
|
25854
|
+
* that are callers of their own (jobs, agents, HTTP triggers) name their surface.
|
|
25855
|
+
*/
|
|
25856
|
+
callToolSurface() {
|
|
25857
|
+
return void 0;
|
|
25858
|
+
}
|
|
25688
25859
|
/**
|
|
25689
25860
|
* Fail the execution and trigger error handling.
|
|
25690
25861
|
*/
|
|
@@ -27175,7 +27346,11 @@ var init_skill_events = __esm({
|
|
|
27175
27346
|
*/
|
|
27176
27347
|
emit(event) {
|
|
27177
27348
|
for (const listener of [...this.listeners]) {
|
|
27178
|
-
|
|
27349
|
+
try {
|
|
27350
|
+
listener(event);
|
|
27351
|
+
} catch (err) {
|
|
27352
|
+
console.error("SkillEmitter listener error:", err instanceof Error ? err.message : "Unknown error");
|
|
27353
|
+
}
|
|
27179
27354
|
}
|
|
27180
27355
|
}
|
|
27181
27356
|
/**
|
|
@@ -27456,6 +27631,24 @@ var init_skill_instance = __esm({
|
|
|
27456
27631
|
// libs/sdk/src/skill/skill.registry.ts
|
|
27457
27632
|
import { tokenName as tokenName9 } from "@frontmcp/di";
|
|
27458
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
|
+
}
|
|
27459
27652
|
var DEFAULT_LIST_LIMIT, SkillRegistry;
|
|
27460
27653
|
var init_skill_registry = __esm({
|
|
27461
27654
|
"libs/sdk/src/skill/skill.registry.ts"() {
|
|
@@ -27487,16 +27680,34 @@ var init_skill_registry = __esm({
|
|
|
27487
27680
|
dynamicContents = /* @__PURE__ */ new Map();
|
|
27488
27681
|
/**
|
|
27489
27682
|
* Per-id generation counter for dynamic registrations. Bumped on every
|
|
27490
|
-
* registerSkillContent call so a stale unregister
|
|
27491
|
-
* 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.
|
|
27492
27686
|
*/
|
|
27493
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();
|
|
27699
|
+
/** Whether a plugin registers skills at runtime ({@link expectDynamicSkills}). */
|
|
27700
|
+
dynamicSkillsExpected = false;
|
|
27494
27701
|
/** Adopted skills from child registries */
|
|
27495
27702
|
adopted = /* @__PURE__ */ new Map();
|
|
27496
27703
|
/** Children registries */
|
|
27497
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();
|
|
27498
27707
|
/** O(1) indexes */
|
|
27499
27708
|
byQualifiedId = /* @__PURE__ */ new Map();
|
|
27709
|
+
/** Owner lineage per skill instance, rebuilt with the index so `lineageOf` is O(1). */
|
|
27710
|
+
lineages = new EntryLineageIndex();
|
|
27500
27711
|
byName = /* @__PURE__ */ new Map();
|
|
27501
27712
|
byOwnerAndName = /* @__PURE__ */ new Map();
|
|
27502
27713
|
/** Version and emitter for change tracking */
|
|
@@ -27577,8 +27788,10 @@ var init_skill_registry = __esm({
|
|
|
27577
27788
|
const row = this.makeRow(token, instance, lineage, this);
|
|
27578
27789
|
this.localRows.push(row);
|
|
27579
27790
|
try {
|
|
27580
|
-
|
|
27581
|
-
|
|
27791
|
+
await this.syncProvider(instance.metadata.id ?? instance.metadata.name, async () => {
|
|
27792
|
+
const content = await instance.load();
|
|
27793
|
+
await this.storageProvider.add(content);
|
|
27794
|
+
});
|
|
27582
27795
|
} catch (error) {
|
|
27583
27796
|
this.scope.logger.warn(`Failed to load skill ${instance.name}: ${error.message}`);
|
|
27584
27797
|
}
|
|
@@ -27601,6 +27814,10 @@ var init_skill_registry = __esm({
|
|
|
27601
27814
|
await this.adoptFromChild(child, child.owner);
|
|
27602
27815
|
}
|
|
27603
27816
|
}
|
|
27817
|
+
const collision = findIdPathCollision(this.listAllInstances());
|
|
27818
|
+
if (collision) {
|
|
27819
|
+
throw new InvalidSkillError(collision.idOwner.metadata.name, describeIdPathCollision(collision));
|
|
27820
|
+
}
|
|
27604
27821
|
this.reindex();
|
|
27605
27822
|
logAvailabilityFiltering(
|
|
27606
27823
|
"SkillRegistry",
|
|
@@ -27620,24 +27837,31 @@ var init_skill_registry = __esm({
|
|
|
27620
27837
|
const adoptedRows = childRows.map((r) => this.relineage(r, prepend));
|
|
27621
27838
|
this.adopted.set(child, adoptedRows);
|
|
27622
27839
|
this.children.add(child);
|
|
27840
|
+
child.adopters.add(this);
|
|
27623
27841
|
child.subscribe({ immediate: false }, () => {
|
|
27842
|
+
const previous = this.adopted.get(child) ?? [];
|
|
27624
27843
|
const latest = child.listAllIndexed().map((r) => this.relineage(r, prepend));
|
|
27625
27844
|
this.adopted.set(child, latest);
|
|
27626
27845
|
this.reindex();
|
|
27627
|
-
this.
|
|
27846
|
+
this.withdrawGoneAdoptedSkills(previous);
|
|
27628
27847
|
for (const row of latest) {
|
|
27629
|
-
row.instance.
|
|
27630
|
-
|
|
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);
|
|
27631
27852
|
}).catch((error) => {
|
|
27632
27853
|
this.scope.logger.warn(`Failed to reload adopted skill ${row.baseName}: ${error.message}`);
|
|
27633
27854
|
});
|
|
27634
27855
|
}
|
|
27856
|
+
this.bump("reset");
|
|
27635
27857
|
});
|
|
27636
27858
|
await Promise.all(
|
|
27637
27859
|
adoptedRows.map(async (row) => {
|
|
27638
27860
|
try {
|
|
27639
|
-
|
|
27640
|
-
|
|
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
|
+
});
|
|
27641
27865
|
} catch (error) {
|
|
27642
27866
|
this.scope.logger.warn(`Failed to load adopted skill ${row.baseName}: ${error.message}`);
|
|
27643
27867
|
}
|
|
@@ -27646,6 +27870,37 @@ var init_skill_registry = __esm({
|
|
|
27646
27870
|
this.reindex();
|
|
27647
27871
|
this.bump("reset");
|
|
27648
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
|
+
}
|
|
27649
27904
|
/* -------------------- Public API -------------------- */
|
|
27650
27905
|
/**
|
|
27651
27906
|
* Get all skills in the registry.
|
|
@@ -27763,7 +28018,14 @@ var init_skill_registry = __esm({
|
|
|
27763
28018
|
}
|
|
27764
28019
|
}
|
|
27765
28020
|
async search(query, options) {
|
|
27766
|
-
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
|
+
}
|
|
27767
28029
|
if (this.dynamicContents.size === 0) return baseResults;
|
|
27768
28030
|
const baseSeen = /* @__PURE__ */ new Set();
|
|
27769
28031
|
let rewrites = 0;
|
|
@@ -27787,7 +28049,6 @@ var init_skill_registry = __esm({
|
|
|
27787
28049
|
const overlayOnly = this.searchDynamicOverlay(query, options, baseSeen);
|
|
27788
28050
|
if (overlayOnly.length === 0 && rewrites === 0) return baseResults;
|
|
27789
28051
|
const merged = [...rewrittenBase, ...overlayOnly].sort((a, b) => b.score - a.score);
|
|
27790
|
-
const topK = options?.topK;
|
|
27791
28052
|
return typeof topK === "number" && topK >= 0 ? merged.slice(0, topK) : merged;
|
|
27792
28053
|
}
|
|
27793
28054
|
/**
|
|
@@ -27842,7 +28103,10 @@ var init_skill_registry = __esm({
|
|
|
27842
28103
|
warning
|
|
27843
28104
|
};
|
|
27844
28105
|
}
|
|
28106
|
+
const hidden = this.hiddenFromProviderRead();
|
|
28107
|
+
if (hidden.has({ name: skillId })) return void 0;
|
|
27845
28108
|
const result = await this.storageProvider.load(skillId);
|
|
28109
|
+
if (result && hidden.has(result.skill)) return void 0;
|
|
27846
28110
|
return result ?? void 0;
|
|
27847
28111
|
}
|
|
27848
28112
|
/**
|
|
@@ -27878,8 +28142,12 @@ var init_skill_registry = __esm({
|
|
|
27878
28142
|
* and `hasMore` stay correct.
|
|
27879
28143
|
*/
|
|
27880
28144
|
async listSkills(options) {
|
|
27881
|
-
|
|
27882
|
-
|
|
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));
|
|
27883
28151
|
const baseIds = /* @__PURE__ */ new Set();
|
|
27884
28152
|
const merged = base.map((meta) => {
|
|
27885
28153
|
const id = meta.id ?? meta.name;
|
|
@@ -27911,30 +28179,48 @@ var init_skill_registry = __esm({
|
|
|
27911
28179
|
if (!page.hasMore || page.skills.length === 0) return all;
|
|
27912
28180
|
}
|
|
27913
28181
|
}
|
|
28182
|
+
/**
|
|
28183
|
+
* Owner lineage (root → leaf) of a skill, or undefined when no row holds it. A skill a plugin
|
|
28184
|
+
* contributes is owned by the plugin; its lineage names the app the plugin is installed on.
|
|
28185
|
+
*/
|
|
28186
|
+
lineageOf(entry) {
|
|
28187
|
+
return this.lineages.lineageOf(entry);
|
|
28188
|
+
}
|
|
27914
28189
|
/**
|
|
27915
28190
|
* Check if any skills exist.
|
|
27916
28191
|
*/
|
|
27917
28192
|
hasAny() {
|
|
27918
28193
|
return this.listAllIndexed().length > 0;
|
|
27919
28194
|
}
|
|
28195
|
+
/**
|
|
28196
|
+
* Record that a plugin registers skills at runtime ({@link registerSkillContent}), such as a
|
|
28197
|
+
* skill bundle loaded after the server starts. The scope then serves the skills capability and
|
|
28198
|
+
* methods from startup, answering with the skills registered so far.
|
|
28199
|
+
*/
|
|
28200
|
+
expectDynamicSkills() {
|
|
28201
|
+
this.dynamicSkillsExpected = true;
|
|
28202
|
+
}
|
|
28203
|
+
/** Whether the scope serves skills: it has some, or a plugin registers them at runtime. */
|
|
28204
|
+
servesSkills() {
|
|
28205
|
+
return this.dynamicSkillsExpected || this.hasAny();
|
|
28206
|
+
}
|
|
27920
28207
|
/**
|
|
27921
28208
|
* Get total skill count.
|
|
27922
28209
|
*
|
|
27923
|
-
*
|
|
28210
|
+
* Counts the provider's rows with overlay rows the provider does not know
|
|
27924
28211
|
* about so callers see the same surface as `search` / `listSkills`.
|
|
27925
28212
|
*/
|
|
27926
28213
|
async count(options) {
|
|
27927
|
-
const
|
|
27928
|
-
if (this.dynamicContents.size === 0)
|
|
27929
|
-
|
|
27930
|
-
|
|
27931
|
-
|
|
27932
|
-
|
|
27933
|
-
});
|
|
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));
|
|
27934
28220
|
const baseIds = /* @__PURE__ */ new Set();
|
|
27935
|
-
for (const m of baseList
|
|
28221
|
+
for (const m of baseList) baseIds.add(m.id ?? m.name);
|
|
27936
28222
|
const overlay = this.collectDynamicMetadata(options).filter((m) => !baseIds.has(m.id ?? m.name));
|
|
27937
|
-
return
|
|
28223
|
+
return baseList.length + overlay.length;
|
|
27938
28224
|
}
|
|
27939
28225
|
/**
|
|
27940
28226
|
* Project a SkillContent into the SkillMetadata shape callers expect from
|
|
@@ -28060,7 +28346,7 @@ var init_skill_registry = __esm({
|
|
|
28060
28346
|
* of the schema cutover see the declaration.
|
|
28061
28347
|
*/
|
|
28062
28348
|
getCapabilities() {
|
|
28063
|
-
if (!this.
|
|
28349
|
+
if (!this.servesSkills()) return {};
|
|
28064
28350
|
return {
|
|
28065
28351
|
experimental: {
|
|
28066
28352
|
[SEP_2640_EXTENSION_ID]: {}
|
|
@@ -28224,10 +28510,6 @@ var init_skill_registry = __esm({
|
|
|
28224
28510
|
throw new PublicMcpError("registerSkillContent: SkillContent.name is required", "INVALID_PARAMS");
|
|
28225
28511
|
}
|
|
28226
28512
|
const id = content.id;
|
|
28227
|
-
const existingIdx = this.dynamicRows.findIndex((r) => r.instance.name === id);
|
|
28228
|
-
if (existingIdx !== -1) {
|
|
28229
|
-
this.dynamicRows.splice(existingIdx, 1);
|
|
28230
|
-
}
|
|
28231
28513
|
const metadata = {
|
|
28232
28514
|
id,
|
|
28233
28515
|
name: content.name,
|
|
@@ -28263,28 +28545,49 @@ var init_skill_registry = __esm({
|
|
|
28263
28545
|
`[SkillRegistry] Failed to pre-load dynamic skill ${id} from source ${opts?.source ?? "unknown"}: ${error.message}`
|
|
28264
28546
|
);
|
|
28265
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
|
+
}
|
|
28266
28567
|
const lineage = this.owner ? [this.owner] : [];
|
|
28267
28568
|
const row = this.makeRow(provideToken, instance, lineage, this);
|
|
28268
28569
|
this.dynamicRows.push(row);
|
|
28269
28570
|
this.dynamicContents.set(id, structuredClone(content));
|
|
28571
|
+
this.withdrawn.delete(id);
|
|
28270
28572
|
const generation = (this.dynamicGenerations.get(id) ?? 0) + 1;
|
|
28271
28573
|
this.dynamicGenerations.set(id, generation);
|
|
28272
|
-
|
|
28273
|
-
|
|
28274
|
-
|
|
28574
|
+
this.reindex();
|
|
28575
|
+
const providerWrites = mutable ? [
|
|
28576
|
+
...removed.map((removedId) => this.removeFromProvider(mutable, removedId)),
|
|
28577
|
+
this.syncProvider(id, async () => {
|
|
28275
28578
|
if (await mutable.exists(id)) {
|
|
28276
28579
|
await mutable.update(id, content);
|
|
28277
28580
|
} else {
|
|
28278
28581
|
await mutable.add(content);
|
|
28279
28582
|
}
|
|
28280
|
-
}
|
|
28583
|
+
}).catch((error) => {
|
|
28281
28584
|
this.scope.logger.warn(
|
|
28282
28585
|
`[SkillRegistry] Failed to index dynamic skill ${id} in storage provider: ${error.message}`
|
|
28283
28586
|
);
|
|
28284
|
-
}
|
|
28285
|
-
|
|
28286
|
-
this.reindex();
|
|
28587
|
+
})
|
|
28588
|
+
] : [];
|
|
28287
28589
|
this.bump("reset");
|
|
28590
|
+
await Promise.all(providerWrites);
|
|
28288
28591
|
let unregistered = false;
|
|
28289
28592
|
const unregister = async () => {
|
|
28290
28593
|
if (unregistered) return;
|
|
@@ -28297,32 +28600,99 @@ var init_skill_registry = __esm({
|
|
|
28297
28600
|
this.scope.logger.debug(
|
|
28298
28601
|
`[SkillRegistry] Registered dynamic skill ${id}${opts?.source ? ` (source: ${opts.source})` : ""}`
|
|
28299
28602
|
);
|
|
28300
|
-
return { id, unregister };
|
|
28603
|
+
return { id, unregister, removed };
|
|
28301
28604
|
}
|
|
28302
28605
|
/**
|
|
28303
28606
|
* Remove a previously-registered dynamic skill by id.
|
|
28304
28607
|
*/
|
|
28305
28608
|
async unregisterSkill(id) {
|
|
28306
|
-
|
|
28307
|
-
if (idx === -1) return false;
|
|
28308
|
-
this.dynamicRows.splice(idx, 1);
|
|
28309
|
-
this.dynamicContents.delete(id);
|
|
28310
|
-
this.dynamicGenerations.delete(id);
|
|
28609
|
+
if (!this.dynamicRows.some((r) => r.instance.name === id)) return false;
|
|
28311
28610
|
const mutable = this.asMutableProvider(this.storageProvider);
|
|
28312
|
-
|
|
28313
|
-
try {
|
|
28314
|
-
await mutable.remove(id);
|
|
28315
|
-
} catch (error) {
|
|
28316
|
-
this.scope.logger.warn(
|
|
28317
|
-
`[SkillRegistry] Failed to remove dynamic skill ${id} from storage provider: ${error.message}`
|
|
28318
|
-
);
|
|
28319
|
-
}
|
|
28320
|
-
}
|
|
28611
|
+
this.withdrawDynamicSkill(id, mutable);
|
|
28321
28612
|
this.reindex();
|
|
28613
|
+
const removal = mutable ? this.removeFromProvider(mutable, id) : void 0;
|
|
28322
28614
|
this.bump("reset");
|
|
28615
|
+
await removal;
|
|
28323
28616
|
this.scope.logger.debug(`[SkillRegistry] Unregistered dynamic skill ${id}`);
|
|
28324
28617
|
return true;
|
|
28325
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
|
+
}
|
|
28326
28696
|
/* -------------------- Internal helpers -------------------- */
|
|
28327
28697
|
asMutableProvider(provider) {
|
|
28328
28698
|
const candidate = provider;
|
|
@@ -28339,6 +28709,7 @@ var init_skill_registry = __esm({
|
|
|
28339
28709
|
}
|
|
28340
28710
|
reindex() {
|
|
28341
28711
|
const effective = this.listAllIndexed();
|
|
28712
|
+
this.lineages.rebuild(effective);
|
|
28342
28713
|
this.byQualifiedId.clear();
|
|
28343
28714
|
this.byName.clear();
|
|
28344
28715
|
this.byOwnerAndName.clear();
|
|
@@ -29263,13 +29634,15 @@ var init_agent_utils = __esm({
|
|
|
29263
29634
|
// libs/sdk/src/agent/agent.instance.ts
|
|
29264
29635
|
import { AuthorityDeniedError as AuthorityDeniedError6 } from "@frontmcp/auth";
|
|
29265
29636
|
import { toJSONSchema as toJSONSchema2, z as z59 } from "@frontmcp/lazy-zod";
|
|
29266
|
-
var VALID_AGENT_HOOK_FLOWS, AGENT_ONLY_METADATA_KEYS, AgentInstance, FunctionAgentContext;
|
|
29637
|
+
var VALID_AGENT_HOOK_FLOWS, AGENT_SURFACE, AGENT_ONLY_METADATA_KEYS, AgentInstance, FunctionAgentContext;
|
|
29267
29638
|
var init_agent_instance = __esm({
|
|
29268
29639
|
"libs/sdk/src/agent/agent.instance.ts"() {
|
|
29269
29640
|
"use strict";
|
|
29270
29641
|
init_config();
|
|
29271
29642
|
init_common();
|
|
29643
|
+
init_availability();
|
|
29272
29644
|
init_tool_decorator();
|
|
29645
|
+
init_call_surface();
|
|
29273
29646
|
init_running_tool();
|
|
29274
29647
|
init_errors();
|
|
29275
29648
|
init_hooks_utils();
|
|
@@ -29279,6 +29652,7 @@ var init_agent_instance = __esm({
|
|
|
29279
29652
|
init_agent_scope();
|
|
29280
29653
|
init_agent_utils();
|
|
29281
29654
|
VALID_AGENT_HOOK_FLOWS = ["agents:call-agent", "agents:list-agents"];
|
|
29655
|
+
AGENT_SURFACE = "agent";
|
|
29282
29656
|
AGENT_ONLY_METADATA_KEYS = /* @__PURE__ */ new Set([
|
|
29283
29657
|
"id",
|
|
29284
29658
|
"name",
|
|
@@ -29301,7 +29675,7 @@ var init_agent_instance = __esm({
|
|
|
29301
29675
|
"hideFromDiscovery"
|
|
29302
29676
|
]);
|
|
29303
29677
|
AgentInstance = class extends AgentEntry {
|
|
29304
|
-
|
|
29678
|
+
providerRegistry;
|
|
29305
29679
|
scope;
|
|
29306
29680
|
hooks;
|
|
29307
29681
|
/** The LLM adapter for this agent */
|
|
@@ -29319,11 +29693,11 @@ var init_agent_instance = __esm({
|
|
|
29319
29693
|
constructor(record, providers, owner) {
|
|
29320
29694
|
super(record);
|
|
29321
29695
|
this.owner = owner;
|
|
29322
|
-
this.
|
|
29696
|
+
this.providerRegistry = providers;
|
|
29323
29697
|
this.name = record.metadata.id ?? record.metadata.name;
|
|
29324
29698
|
this.id = record.metadata.id ?? record.metadata.name;
|
|
29325
29699
|
this.fullName = this.owner.id + ":" + this.name;
|
|
29326
|
-
this.scope = this.
|
|
29700
|
+
this.scope = this.providerRegistry.getActiveScope();
|
|
29327
29701
|
this.hooks = this.scope.hooks;
|
|
29328
29702
|
this.inputSchema = record.metadata.inputSchema ?? {};
|
|
29329
29703
|
this.outputSchema = record.metadata.outputSchema;
|
|
@@ -29382,11 +29756,11 @@ var init_agent_instance = __esm({
|
|
|
29382
29756
|
const adapterOptions = {
|
|
29383
29757
|
providerResolver: {
|
|
29384
29758
|
get: (token) => {
|
|
29385
|
-
return this.
|
|
29759
|
+
return this.providerRegistry.get(token);
|
|
29386
29760
|
},
|
|
29387
29761
|
tryGet: (token) => {
|
|
29388
29762
|
try {
|
|
29389
|
-
return this.
|
|
29763
|
+
return this.providerRegistry.get(token);
|
|
29390
29764
|
} catch {
|
|
29391
29765
|
return void 0;
|
|
29392
29766
|
}
|
|
@@ -29399,7 +29773,7 @@ var init_agent_instance = __esm({
|
|
|
29399
29773
|
}
|
|
29400
29774
|
};
|
|
29401
29775
|
try {
|
|
29402
|
-
const configService = this.
|
|
29776
|
+
const configService = this.providerRegistry.get(ConfigService);
|
|
29403
29777
|
if (configService) {
|
|
29404
29778
|
adapterOptions.configResolver = this.createConfigResolver(configService);
|
|
29405
29779
|
}
|
|
@@ -29507,7 +29881,7 @@ var init_agent_instance = __esm({
|
|
|
29507
29881
|
const toolMetadata = this.buildAgentToolMetadata();
|
|
29508
29882
|
const agentToolFunction = frontMcpTool(toolMetadata)(this.createAgentToolExecuteHandler());
|
|
29509
29883
|
const toolRecord = normalizeTool(agentToolFunction);
|
|
29510
|
-
this.agentToolInstance = new ToolInstance(toolRecord, this.
|
|
29884
|
+
this.agentToolInstance = new ToolInstance(toolRecord, this.providerRegistry, this.owner);
|
|
29511
29885
|
await this.agentToolInstance.ready;
|
|
29512
29886
|
this.scope.logger.debug(`Created agent tool instance: ${this.agentToolInstance.name} for agent ${this.name}`);
|
|
29513
29887
|
} catch (error) {
|
|
@@ -29549,16 +29923,34 @@ var init_agent_instance = __esm({
|
|
|
29549
29923
|
/**
|
|
29550
29924
|
* Create the execute handler for the agent tool.
|
|
29551
29925
|
*
|
|
29552
|
-
*
|
|
29553
|
-
*
|
|
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.
|
|
29554
29932
|
*/
|
|
29555
29933
|
createAgentToolExecuteHandler() {
|
|
29556
29934
|
return async (input, toolCtx) => {
|
|
29557
29935
|
const authInfo = toolCtx.authInfo;
|
|
29558
|
-
const
|
|
29559
|
-
const
|
|
29560
|
-
|
|
29561
|
-
|
|
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
|
+
});
|
|
29562
29954
|
};
|
|
29563
29955
|
}
|
|
29564
29956
|
/**
|
|
@@ -29572,6 +29964,10 @@ var init_agent_instance = __esm({
|
|
|
29572
29964
|
getToolInstance() {
|
|
29573
29965
|
return this.agentToolInstance;
|
|
29574
29966
|
}
|
|
29967
|
+
/** The provider registry this agent was registered with (its app's). */
|
|
29968
|
+
get providers() {
|
|
29969
|
+
return this.providerRegistry;
|
|
29970
|
+
}
|
|
29575
29971
|
/**
|
|
29576
29972
|
* The tools this agent can call: those it declares and those its plugins contribute, as its private
|
|
29577
29973
|
* scope holds them. Empty until the agent is initialized.
|
|
@@ -29621,7 +30017,7 @@ var init_agent_instance = __esm({
|
|
|
29621
30017
|
case "VALUE" /* VALUE */:
|
|
29622
30018
|
return this.record.useValue;
|
|
29623
30019
|
case "FACTORY" /* FACTORY */:
|
|
29624
|
-
return this.record.useFactory(this.
|
|
30020
|
+
return this.record.useFactory(this.providerRegistry);
|
|
29625
30021
|
case "ESM" /* ESM */:
|
|
29626
30022
|
throw new AgentNotConfiguredError(`ESM agent "${this.name}" cannot be created via AgentInstance.create()`);
|
|
29627
30023
|
case "REMOTE" /* REMOTE */:
|
|
@@ -29640,11 +30036,13 @@ var init_agent_instance = __esm({
|
|
|
29640
30036
|
* @param llmAdapter - The LLM adapter (passed explicitly to avoid non-null assertion)
|
|
29641
30037
|
*/
|
|
29642
30038
|
buildAgentCtorArgs(input, ctx, llmAdapter) {
|
|
29643
|
-
const scope = this.
|
|
30039
|
+
const scope = this.providerRegistry.getActiveScope();
|
|
29644
30040
|
return {
|
|
29645
30041
|
metadata: this.metadata,
|
|
29646
30042
|
input,
|
|
29647
|
-
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,
|
|
29648
30046
|
logger: scope.logger,
|
|
29649
30047
|
authInfo: ctx.authInfo,
|
|
29650
30048
|
llmAdapter,
|
|
@@ -29655,10 +30053,17 @@ var init_agent_instance = __esm({
|
|
|
29655
30053
|
}
|
|
29656
30054
|
/**
|
|
29657
30055
|
* Get tool definitions available to this agent.
|
|
29658
|
-
* Returns only the agent's own tools (from @Agent({ tools: [...] })).
|
|
30056
|
+
* Returns only the agent's own tools (from @Agent({ tools: [...] })) that are offered to it.
|
|
29659
30057
|
*/
|
|
29660
30058
|
getToolDefinitions() {
|
|
29661
|
-
return buildAgentToolDefinitions(this.
|
|
30059
|
+
return buildAgentToolDefinitions(this.getOfferedTools());
|
|
30060
|
+
}
|
|
30061
|
+
/**
|
|
30062
|
+
* The agent's tools its model may call: those whose `availableWhen.surface` offers them on the
|
|
30063
|
+
* `'agent'` surface. The others are neither shown to the model nor run for it.
|
|
30064
|
+
*/
|
|
30065
|
+
getOfferedTools() {
|
|
30066
|
+
return this.agentTools.filter((tool) => isOfferedOnSurface(tool.metadata.availableWhen, AGENT_SURFACE));
|
|
29662
30067
|
}
|
|
29663
30068
|
/**
|
|
29664
30069
|
* Create a tool executor for agent-scoped tools.
|
|
@@ -29681,12 +30086,13 @@ var init_agent_instance = __esm({
|
|
|
29681
30086
|
createToolExecutor(ctx) {
|
|
29682
30087
|
const useToolFlow = this.record.metadata.execution?.useToolFlow !== false;
|
|
29683
30088
|
return async (toolName, args) => {
|
|
29684
|
-
const
|
|
30089
|
+
const offeredTools = this.getOfferedTools();
|
|
30090
|
+
const tool = offeredTools.find((t) => t.name === toolName || t.fullName === toolName);
|
|
29685
30091
|
if (!tool) {
|
|
29686
30092
|
throw new AgentToolNotFoundError(
|
|
29687
30093
|
this.name,
|
|
29688
30094
|
toolName,
|
|
29689
|
-
|
|
30095
|
+
offeredTools.map((t) => t.name)
|
|
29690
30096
|
);
|
|
29691
30097
|
}
|
|
29692
30098
|
if (useToolFlow && this.agentScope) {
|
|
@@ -29697,16 +30103,20 @@ var init_agent_instance = __esm({
|
|
|
29697
30103
|
},
|
|
29698
30104
|
ctx: {
|
|
29699
30105
|
authInfo: ctx.authInfo,
|
|
29700
|
-
_skipUI: true
|
|
30106
|
+
_skipUI: true,
|
|
29701
30107
|
// Skip UI rendering - agent returns structured data
|
|
30108
|
+
surface: AGENT_SURFACE
|
|
29702
30109
|
}
|
|
29703
30110
|
});
|
|
29704
30111
|
return this.extractToolResult(result);
|
|
29705
30112
|
}
|
|
29706
30113
|
await this.assertToolAuthorized(tool, ctx.authInfo, args);
|
|
29707
30114
|
const runningTool = { name: tool.name, fullName: tool.fullName };
|
|
29708
|
-
|
|
29709
|
-
|
|
30115
|
+
return runOnSurface(AGENT_SURFACE, () => {
|
|
30116
|
+
const { contextProviders: _agentProviders, ...toolCtx } = ctx;
|
|
30117
|
+
const toolContext = runAsTool(runningTool, () => tool.create(args, toolCtx));
|
|
30118
|
+
return runAsTool(runningTool, () => Promise.resolve(toolContext.execute(args)));
|
|
30119
|
+
});
|
|
29710
30120
|
};
|
|
29711
30121
|
}
|
|
29712
30122
|
/**
|
|
@@ -30243,6 +30653,53 @@ var init_agent_registry = __esm({
|
|
|
30243
30653
|
}
|
|
30244
30654
|
});
|
|
30245
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
|
+
|
|
30246
30703
|
// libs/sdk/src/agent/flows/call-agent.flow.ts
|
|
30247
30704
|
import { AuthorityDeniedError as AuthorityDeniedError7, resolveRequiredScopes as resolveRequiredScopes5 } from "@frontmcp/auth";
|
|
30248
30705
|
import { ExecutionTimeoutError as ExecutionTimeoutError2, withTimeout as withTimeout2 } from "@frontmcp/guard";
|
|
@@ -30252,13 +30709,22 @@ var inputSchema15, outputSchema14, stateSchema14, plan14, name14, Stage14, CallA
|
|
|
30252
30709
|
var init_call_agent_flow = __esm({
|
|
30253
30710
|
"libs/sdk/src/agent/flows/call-agent.flow.ts"() {
|
|
30254
30711
|
"use strict";
|
|
30712
|
+
init_completion_events();
|
|
30255
30713
|
init_common();
|
|
30256
30714
|
init_errors();
|
|
30257
30715
|
init_hooks_utils();
|
|
30716
|
+
init_flow_context_providers();
|
|
30258
30717
|
inputSchema15 = z60.object({
|
|
30259
30718
|
request: CallToolRequestSchema3,
|
|
30260
30719
|
// z.any() used because ctx is the MCP SDK's ToolCallExtra type which varies by SDK version
|
|
30261
|
-
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()
|
|
30262
30728
|
});
|
|
30263
30729
|
outputSchema14 = CallToolResultSchema3;
|
|
30264
30730
|
stateSchema14 = z60.object({
|
|
@@ -30291,7 +30757,13 @@ var init_call_agent_flow = __esm({
|
|
|
30291
30757
|
// Semaphore ticket for concurrency control (set by acquireSemaphore, used by releaseSemaphore)
|
|
30292
30758
|
semaphoreTicket: z60.any().optional(),
|
|
30293
30759
|
// A timed-out execute() that is still running; its concurrency slot is released when it settles
|
|
30294
|
-
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()
|
|
30295
30767
|
});
|
|
30296
30768
|
plan14 = {
|
|
30297
30769
|
pre: [
|
|
@@ -30304,12 +30776,16 @@ var init_call_agent_flow = __esm({
|
|
|
30304
30776
|
"acquireSemaphore"
|
|
30305
30777
|
],
|
|
30306
30778
|
execute: ["validateInput", "execute", "validateOutput"],
|
|
30307
|
-
finalize: ["releaseSemaphore", "releaseQuota", "finalize"]
|
|
30779
|
+
finalize: ["releaseSemaphore", "releaseQuota", "parseOutput", "emitCompletion", "finalize"]
|
|
30308
30780
|
};
|
|
30309
30781
|
name14 = "agents:call-agent";
|
|
30310
30782
|
({ Stage: Stage14 } = FlowHooksOf(name14));
|
|
30311
30783
|
CallAgentFlow = class extends FlowBase {
|
|
30312
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
|
+
}
|
|
30313
30789
|
async parseInput() {
|
|
30314
30790
|
this.logger.verbose("parseInput:start");
|
|
30315
30791
|
let method;
|
|
@@ -30376,6 +30852,10 @@ var init_call_agent_flow = __esm({
|
|
|
30376
30852
|
}
|
|
30377
30853
|
async checkEntryAuthorities() {
|
|
30378
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
|
+
}
|
|
30379
30859
|
const engine = this.scope.authoritiesEngine;
|
|
30380
30860
|
const ctxBuilder = this.scope.authoritiesContextBuilder;
|
|
30381
30861
|
if (!engine || !ctxBuilder) {
|
|
@@ -30441,14 +30921,17 @@ var init_call_agent_flow = __esm({
|
|
|
30441
30921
|
const progressToken = this.state.progressToken;
|
|
30442
30922
|
const authInfo = this.state.authInfo;
|
|
30443
30923
|
try {
|
|
30444
|
-
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 });
|
|
30445
30928
|
this.appendContextHooks(hooksBoundTo(this.scope.hooks.getClsHooks(agent.record.provide), context));
|
|
30446
30929
|
context.mark("createAgentContext");
|
|
30447
30930
|
const frontmcpContext = context.tryGetContext();
|
|
30448
30931
|
const sdkAuthInfo = authInfo;
|
|
30449
|
-
|
|
30932
|
+
const jsonRpcRequestId = this.state.jsonRpcRequestId;
|
|
30933
|
+
if (frontmcpContext && sdkAuthInfo?.transport?.sendElicitRequest && jsonRpcRequestId !== void 0) {
|
|
30450
30934
|
const transport = sdkAuthInfo.transport;
|
|
30451
|
-
const jsonRpcRequestId = this.state.jsonRpcRequestId;
|
|
30452
30935
|
frontmcpContext.setTransport({
|
|
30453
30936
|
sendElicitRequest: transport.sendElicitRequest.bind(transport),
|
|
30454
30937
|
type: transport.type ?? "unknown",
|
|
@@ -30465,7 +30948,7 @@ var init_call_agent_flow = __esm({
|
|
|
30465
30948
|
async acquireQuota() {
|
|
30466
30949
|
this.logger.verbose("acquireQuota:start");
|
|
30467
30950
|
const manager = this.scope.rateLimitManager;
|
|
30468
|
-
if (!manager) {
|
|
30951
|
+
if (!manager || this.gatedByToolFlow) {
|
|
30469
30952
|
this.state.agentContext?.mark("acquireQuota");
|
|
30470
30953
|
this.logger.verbose("acquireQuota:done (no rate limit manager)");
|
|
30471
30954
|
return;
|
|
@@ -30489,7 +30972,7 @@ var init_call_agent_flow = __esm({
|
|
|
30489
30972
|
async acquireSemaphore() {
|
|
30490
30973
|
this.logger.verbose("acquireSemaphore:start");
|
|
30491
30974
|
const manager = this.scope.rateLimitManager;
|
|
30492
|
-
if (!manager) {
|
|
30975
|
+
if (!manager || this.gatedByToolFlow) {
|
|
30493
30976
|
this.state.agentContext?.mark("acquireSemaphore");
|
|
30494
30977
|
this.logger.verbose("acquireSemaphore:done (no rate limit manager)");
|
|
30495
30978
|
return;
|
|
@@ -30534,7 +31017,8 @@ var init_call_agent_flow = __esm({
|
|
|
30534
31017
|
}
|
|
30535
31018
|
agentContext.mark("execute");
|
|
30536
31019
|
const startTime = Date.now();
|
|
30537
|
-
|
|
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;
|
|
30538
31022
|
const running = (async () => {
|
|
30539
31023
|
agentContext.output = await agentContext.execute(agentContext.input);
|
|
30540
31024
|
})();
|
|
@@ -30545,6 +31029,7 @@ var init_call_agent_flow = __esm({
|
|
|
30545
31029
|
});
|
|
30546
31030
|
this.logger.verbose("execute:done");
|
|
30547
31031
|
} catch (error) {
|
|
31032
|
+
this.state.set("executionError", error);
|
|
30548
31033
|
if (error instanceof ExecutionTimeoutError2) {
|
|
30549
31034
|
this.logger.warn("execute: agent execution timed out", {
|
|
30550
31035
|
agent: agent.metadata.name,
|
|
@@ -30553,6 +31038,7 @@ var init_call_agent_flow = __esm({
|
|
|
30553
31038
|
this.state.set("abandonedExecution", running);
|
|
30554
31039
|
throw error;
|
|
30555
31040
|
}
|
|
31041
|
+
if (this.gatedByToolFlow) throw error;
|
|
30556
31042
|
throw new AgentExecutionError(agent.metadata.name, error instanceof Error ? error : void 0);
|
|
30557
31043
|
}
|
|
30558
31044
|
}
|
|
@@ -30590,9 +31076,34 @@ var init_call_agent_flow = __esm({
|
|
|
30590
31076
|
this.state.agentContext?.mark("releaseQuota");
|
|
30591
31077
|
this.logger.verbose("releaseQuota:done");
|
|
30592
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
|
+
}
|
|
30593
31104
|
async finalize() {
|
|
30594
31105
|
this.logger.verbose("finalize:start");
|
|
30595
|
-
const { agent, rawOutput, executionMeta } = this.state;
|
|
31106
|
+
const { agent, rawOutput, parsedOutput, outputError, executionMeta } = this.state;
|
|
30596
31107
|
if (!agent) {
|
|
30597
31108
|
this.logger.verbose("finalize: skipping (no agent in state)");
|
|
30598
31109
|
return;
|
|
@@ -30601,15 +31112,14 @@ var init_call_agent_flow = __esm({
|
|
|
30601
31112
|
this.logger.verbose("finalize: skipping (no output - execute stage likely failed)");
|
|
30602
31113
|
return;
|
|
30603
31114
|
}
|
|
30604
|
-
|
|
30605
|
-
if (!parseResult.success) {
|
|
31115
|
+
if (outputError !== void 0 || parsedOutput === void 0) {
|
|
30606
31116
|
this.logger.error("finalize: output validation failed", {
|
|
30607
31117
|
agent: agent.metadata.name,
|
|
30608
|
-
errors:
|
|
31118
|
+
errors: outputError
|
|
30609
31119
|
});
|
|
30610
31120
|
throw new InvalidOutputError();
|
|
30611
31121
|
}
|
|
30612
|
-
const result =
|
|
31122
|
+
const result = parsedOutput;
|
|
30613
31123
|
if (executionMeta) {
|
|
30614
31124
|
result._meta = {
|
|
30615
31125
|
...result._meta,
|
|
@@ -30675,6 +31185,12 @@ var init_call_agent_flow = __esm({
|
|
|
30675
31185
|
__decorateClass([
|
|
30676
31186
|
Stage14("releaseQuota")
|
|
30677
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);
|
|
30678
31194
|
__decorateClass([
|
|
30679
31195
|
Stage14("finalize")
|
|
30680
31196
|
], CallAgentFlow.prototype, "finalize", 1);
|
|
@@ -31476,10 +31992,17 @@ var init_mrtr = __esm({
|
|
|
31476
31992
|
/** Per-kind call counters, used to derive stable keys across a replay. */
|
|
31477
31993
|
counters = { elicitation: 0, sampling: 0, roots: 0 };
|
|
31478
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;
|
|
31479
32001
|
binding;
|
|
31480
32002
|
constructor(params) {
|
|
31481
32003
|
this.responses = { ...params.carriedResponses ?? {}, ...params.inputResponses ?? {} };
|
|
31482
32004
|
this.clientCapabilities = params.clientCapabilities;
|
|
32005
|
+
this.clientDeclaredRevision = params.clientDeclaredRevision ?? true;
|
|
31483
32006
|
this.binding = params.binding;
|
|
31484
32007
|
}
|
|
31485
32008
|
/** True when the client declared the capability a given request kind needs. */
|
|
@@ -31832,7 +32355,7 @@ function shouldCreateTask(params) {
|
|
|
31832
32355
|
return { kind: "create", owner: ownership.owner };
|
|
31833
32356
|
}
|
|
31834
32357
|
async function resumeTask(params) {
|
|
31835
|
-
const { scope, record, authInfo, clientCapabilities, frontmcpContext } = params;
|
|
32358
|
+
const { scope, record, authInfo, clientCapabilities, frontmcpContext, clientDeclaredRevision } = params;
|
|
31836
32359
|
const registry = scope.tasks;
|
|
31837
32360
|
const runner = registry?.runner;
|
|
31838
32361
|
if (!runner) {
|
|
@@ -31843,6 +32366,8 @@ async function resumeTask(params) {
|
|
|
31843
32366
|
new MrtrExchange({
|
|
31844
32367
|
carriedResponses: record.inputResponses ?? {},
|
|
31845
32368
|
clientCapabilities,
|
|
32369
|
+
// A caller that never declared the revision is refused an elicitation as a legacy caller is.
|
|
32370
|
+
clientDeclaredRevision,
|
|
31846
32371
|
binding: {
|
|
31847
32372
|
principal: resolveTaskPrincipal(authInfo),
|
|
31848
32373
|
binding: computeRequestBinding("tasks/resume", { name: record.taskId })
|
|
@@ -31889,6 +32414,7 @@ async function dispatch20260728(options) {
|
|
|
31889
32414
|
scope,
|
|
31890
32415
|
body,
|
|
31891
32416
|
clientCapabilities,
|
|
32417
|
+
clientDeclaredRevision,
|
|
31892
32418
|
frontmcpContext,
|
|
31893
32419
|
authInfo,
|
|
31894
32420
|
isAnonymous,
|
|
@@ -31935,7 +32461,8 @@ async function dispatch20260728(options) {
|
|
|
31935
32461
|
record,
|
|
31936
32462
|
authInfo: { ...authInfo ?? {}, sessionId: ownership.owner },
|
|
31937
32463
|
clientCapabilities,
|
|
31938
|
-
frontmcpContext
|
|
32464
|
+
frontmcpContext,
|
|
32465
|
+
clientDeclaredRevision
|
|
31939
32466
|
})
|
|
31940
32467
|
});
|
|
31941
32468
|
return outcome.kind === "result" ? { kind: "result", result: decorate(outcome.result) } : outcome;
|
|
@@ -31965,7 +32492,8 @@ async function dispatch20260728(options) {
|
|
|
31965
32492
|
inputResponses: params["inputResponses"],
|
|
31966
32493
|
carriedResponses: carried.ok ? carried.responses : {},
|
|
31967
32494
|
clientCapabilities,
|
|
31968
|
-
binding
|
|
32495
|
+
binding,
|
|
32496
|
+
clientDeclaredRevision
|
|
31969
32497
|
});
|
|
31970
32498
|
frontmcpContext?.setMrtrExchange(exchange);
|
|
31971
32499
|
if (notificationSink) frontmcpContext?.setRequestNotificationSink(notificationSink);
|
|
@@ -35442,13 +35970,13 @@ var init_esm_module_loader = __esm({
|
|
|
35442
35970
|
URL.revokeObjectURL(url);
|
|
35443
35971
|
}
|
|
35444
35972
|
}
|
|
35445
|
-
const { ensureDir, fileExists: fileExists9, sha256Hex:
|
|
35973
|
+
const { ensureDir, fileExists: fileExists9, sha256Hex: sha256Hex14, writeFile } = await import("@frontmcp/utils");
|
|
35446
35974
|
const nodePath = await import("node:path");
|
|
35447
35975
|
const nodeOs = await import("node:os");
|
|
35448
35976
|
const { pathToFileURL } = await import("node:url");
|
|
35449
35977
|
const dir = nodePath.join(nodeOs.tmpdir(), "frontmcp-esm-modules");
|
|
35450
35978
|
await ensureDir(dir);
|
|
35451
|
-
const tempPath = nodePath.join(dir, `${
|
|
35979
|
+
const tempPath = nodePath.join(dir, `${sha256Hex14(bundleContent)}.mjs`);
|
|
35452
35980
|
if (!await fileExists9(tempPath)) {
|
|
35453
35981
|
await writeFile(tempPath, bundleContent);
|
|
35454
35982
|
}
|
|
@@ -37187,7 +37715,7 @@ var init_oauth_authorize_flow = __esm({
|
|
|
37187
37715
|
});
|
|
37188
37716
|
inputSchema16 = httpInputSchema;
|
|
37189
37717
|
stateSchema15 = z65.object({
|
|
37190
|
-
isDefaultAuthProvider: z65.boolean().describe("
|
|
37718
|
+
isDefaultAuthProvider: z65.boolean().describe("No sign-in to run: no auth options, or public mode (the same configuration, written out)"),
|
|
37191
37719
|
isOrchestrated: z65.boolean().describe("If FrontMcp is orchestrated (local oauth proxy, remote oauth proxy)"),
|
|
37192
37720
|
allowAnonymous: z65.boolean().describe("Allow anonymous access, force orchestrated mode"),
|
|
37193
37721
|
// Validated OAuth request (after validation)
|
|
@@ -37253,9 +37781,13 @@ var init_oauth_authorize_flow = __esm({
|
|
|
37253
37781
|
* Returns `undefined` when the configured auth instance has no issuer, so the
|
|
37254
37782
|
* parameter is simply omitted rather than emitted empty — a client validating
|
|
37255
37783
|
* `iss` treats absence as "not supported" and proceeds.
|
|
37784
|
+
*
|
|
37785
|
+
* With `request`, the issuer the tokens redeemed for this request name
|
|
37786
|
+
* (`LocalPrimaryAuth.issuerFor`); without it, the boot-time issuer.
|
|
37256
37787
|
*/
|
|
37257
|
-
resolveIssuer() {
|
|
37258
|
-
const
|
|
37788
|
+
resolveIssuer(request) {
|
|
37789
|
+
const auth = this.scope.auth;
|
|
37790
|
+
const issuer = request && auth?.issuerFor ? auth.issuerFor(request) : auth?.issuer;
|
|
37259
37791
|
return typeof issuer === "string" && issuer.length > 0 ? issuer : void 0;
|
|
37260
37792
|
}
|
|
37261
37793
|
logger = this.scope.logger.child("OauthAuthorizeFlow");
|
|
@@ -37295,7 +37827,7 @@ var init_oauth_authorize_flow = __esm({
|
|
|
37295
37827
|
}
|
|
37296
37828
|
}
|
|
37297
37829
|
const existingSessionId = request.query["session_id"];
|
|
37298
|
-
const isDefaultAuthProvider = !metadata.auth;
|
|
37830
|
+
const isDefaultAuthProvider = !metadata.auth || isPublicMode(metadata.auth);
|
|
37299
37831
|
let requiresFederatedLogin = false;
|
|
37300
37832
|
if (metadata.auth && isOrchestratedMode(metadata.auth)) {
|
|
37301
37833
|
const configProviders = getConfiguredProviderIds(metadata.auth);
|
|
@@ -37368,11 +37900,26 @@ var init_oauth_authorize_flow = __esm({
|
|
|
37368
37900
|
if (result2.data.state) {
|
|
37369
37901
|
url.searchParams.set("state", result2.data.state);
|
|
37370
37902
|
}
|
|
37371
|
-
const issuer = this.resolveIssuer();
|
|
37903
|
+
const issuer = this.resolveIssuer(this.rawInput.request);
|
|
37372
37904
|
if (issuer) url.searchParams.set("iss", issuer);
|
|
37373
37905
|
this.respond(httpRespond.redirect(url.toString()));
|
|
37374
37906
|
return;
|
|
37375
37907
|
}
|
|
37908
|
+
const authConfig = this.scope.metadata.auth;
|
|
37909
|
+
if (!authConfig || !isOrchestratedMode(authConfig)) {
|
|
37910
|
+
const mode = authConfig?.mode ?? "public";
|
|
37911
|
+
this.logger.warn(`Authorization request refused: auth mode ${mode} does not sign users in`);
|
|
37912
|
+
this.respond(
|
|
37913
|
+
this.htmlPage(
|
|
37914
|
+
this.renderErrorPage(
|
|
37915
|
+
"unsupported_response_type",
|
|
37916
|
+
`This server does not sign users in (auth mode: ${mode}). Present its access token instead.`
|
|
37917
|
+
),
|
|
37918
|
+
400
|
|
37919
|
+
)
|
|
37920
|
+
);
|
|
37921
|
+
return;
|
|
37922
|
+
}
|
|
37376
37923
|
const result = oauthAuthorizeRequestSchema.safeParse({
|
|
37377
37924
|
response_type: request.query["response_type"],
|
|
37378
37925
|
client_id: request.query["client_id"],
|
|
@@ -37408,10 +37955,10 @@ var init_oauth_authorize_flow = __esm({
|
|
|
37408
37955
|
const redirectAllowlisted = !!registry?.hasRedirectAllowlist() && registry.isRedirectUriAllowed(redirect_uri);
|
|
37409
37956
|
let redirectVerified = redirectAllowlisted;
|
|
37410
37957
|
const isCimdClientId2 = !!cimdService?.enabled && cimdService.isCimdClientId(client_id);
|
|
37411
|
-
const
|
|
37412
|
-
if (
|
|
37413
|
-
this.logger.warn(`OAuth authorize: DCR allowlist rejection \u2014 ${
|
|
37414
|
-
this.respondWithError([
|
|
37958
|
+
const redirectError = this.checkDcrRedirectAllowlist(redirect_uri);
|
|
37959
|
+
if (redirectError) {
|
|
37960
|
+
this.logger.warn(`OAuth authorize: DCR allowlist rejection \u2014 ${redirectError}`);
|
|
37961
|
+
this.respondWithError([redirectError], void 0, rawState);
|
|
37415
37962
|
return;
|
|
37416
37963
|
}
|
|
37417
37964
|
if (isCimdClientId2) {
|
|
@@ -37435,24 +37982,34 @@ var init_oauth_authorize_flow = __esm({
|
|
|
37435
37982
|
return;
|
|
37436
37983
|
}
|
|
37437
37984
|
}
|
|
37438
|
-
|
|
37439
|
-
|
|
37440
|
-
if (registered) {
|
|
37441
|
-
|
|
37442
|
-
|
|
37443
|
-
this.respondWithError(["redirect_uri is not registered for this client"], void 0, rawState);
|
|
37444
|
-
return;
|
|
37445
|
-
}
|
|
37446
|
-
redirectVerified = true;
|
|
37447
|
-
} else if (authOptions?.requireRegisteredClients !== false) {
|
|
37448
|
-
this.logger.warn(`OAuth authorize: rejecting unregistered client_id "${client_id}" (requireRegisteredClients)`);
|
|
37449
|
-
this.respondWithError(
|
|
37450
|
-
["Unknown client_id: register the client (DCR / pre-registered) or use a CIMD client-id URL"],
|
|
37451
|
-
void 0,
|
|
37452
|
-
rawState
|
|
37453
|
-
);
|
|
37985
|
+
const registered = isCimdClientId2 ? void 0 : registry?.get?.(client_id);
|
|
37986
|
+
if (registered) {
|
|
37987
|
+
if (!registered.redirect_uris.includes(redirect_uri)) {
|
|
37988
|
+
this.logger.warn(`OAuth authorize: redirect_uri "${redirect_uri}" not registered for client "${client_id}"`);
|
|
37989
|
+
this.respondWithError(["redirect_uri is not registered for this client"], void 0, rawState);
|
|
37454
37990
|
return;
|
|
37455
37991
|
}
|
|
37992
|
+
redirectVerified = true;
|
|
37993
|
+
}
|
|
37994
|
+
const clientIdError = isCimdClientId2 ? void 0 : this.checkDcrClientIdAllowlist(client_id);
|
|
37995
|
+
if (clientIdError) {
|
|
37996
|
+
this.logger.warn(`OAuth authorize: DCR allowlist rejection \u2014 ${clientIdError}`);
|
|
37997
|
+
this.respondWithError(
|
|
37998
|
+
[clientIdError],
|
|
37999
|
+
redirectVerified ? redirect_uri : void 0,
|
|
38000
|
+
rawState,
|
|
38001
|
+
"unauthorized_client"
|
|
38002
|
+
);
|
|
38003
|
+
return;
|
|
38004
|
+
}
|
|
38005
|
+
if (!isCimdClientId2 && !registered && authOptions?.requireRegisteredClients !== false) {
|
|
38006
|
+
this.logger.warn(`OAuth authorize: rejecting unregistered client_id "${client_id}" (requireRegisteredClients)`);
|
|
38007
|
+
this.respondWithError(
|
|
38008
|
+
["Unknown client_id: register the client (DCR / pre-registered) or use a CIMD client-id URL"],
|
|
38009
|
+
void 0,
|
|
38010
|
+
rawState
|
|
38011
|
+
);
|
|
38012
|
+
return;
|
|
37456
38013
|
}
|
|
37457
38014
|
const canonicalResource = computeResource(request, this.scope.entryPath, this.scope.routeBase);
|
|
37458
38015
|
if (!validatedRequest.resource) {
|
|
@@ -37893,26 +38450,28 @@ var init_oauth_authorize_flow = __esm({
|
|
|
37893
38450
|
return httpRespond.html(markup, status, builtInAuthPageHeaders());
|
|
37894
38451
|
}
|
|
37895
38452
|
/**
|
|
37896
|
-
* Enforce the local-AS DCR
|
|
37897
|
-
*
|
|
37898
|
-
*
|
|
37899
|
-
*
|
|
37900
|
-
*
|
|
37901
|
-
* - `allowedRedirectUris`: applies to ALL clients (CIMD or not) as an
|
|
37902
|
-
* open-redirect / lateral-movement guard.
|
|
37903
|
-
* - `allowedClientIds`: applies only to non-CIMD client ids; CIMD URLs are
|
|
37904
|
-
* validated by the CIMD layer instead.
|
|
38453
|
+
* Enforce the local-AS DCR `allowedRedirectUris` (#462) at authorize time,
|
|
38454
|
+
* for ALL clients (CIMD or not) as an open-redirect / lateral-movement guard.
|
|
38455
|
+
* Returns a human-readable rejection reason, or `undefined` when the
|
|
38456
|
+
* redirect_uri is allowed (including when no allowlist is configured, or the
|
|
38457
|
+
* auth instance does not expose a DCR registry — e.g. non-local modes).
|
|
37905
38458
|
*/
|
|
37906
|
-
|
|
37907
|
-
const
|
|
37908
|
-
|
|
37909
|
-
if (!registry) {
|
|
37910
|
-
return void 0;
|
|
37911
|
-
}
|
|
37912
|
-
if (registry.hasRedirectAllowlist() && !registry.isRedirectUriAllowed(redirectUri)) {
|
|
38459
|
+
checkDcrRedirectAllowlist(redirectUri) {
|
|
38460
|
+
const registry = this.scope.auth?.dcrClientRegistry;
|
|
38461
|
+
if (registry?.hasRedirectAllowlist() && !registry.isRedirectUriAllowed(redirectUri)) {
|
|
37913
38462
|
return `redirect_uri "${redirectUri}" is not in the configured allowlist`;
|
|
37914
38463
|
}
|
|
37915
|
-
|
|
38464
|
+
return void 0;
|
|
38465
|
+
}
|
|
38466
|
+
/**
|
|
38467
|
+
* Enforce the local-AS DCR `allowedClientIds` (#462) at authorize time, for
|
|
38468
|
+
* non-CIMD client ids (the caller skips CIMD URLs, which the CIMD layer
|
|
38469
|
+
* validates). Returns a human-readable rejection reason, or `undefined` when
|
|
38470
|
+
* the client is allowed or no allowlist is configured.
|
|
38471
|
+
*/
|
|
38472
|
+
checkDcrClientIdAllowlist(clientId) {
|
|
38473
|
+
const registry = this.scope.auth?.dcrClientRegistry;
|
|
38474
|
+
if (registry?.hasClientIdAllowlist() && !registry.isClientIdAllowed(clientId)) {
|
|
37916
38475
|
return `client_id "${clientId}" is not in the configured allowlist`;
|
|
37917
38476
|
}
|
|
37918
38477
|
return void 0;
|
|
@@ -37927,24 +38486,29 @@ var init_oauth_authorize_flow = __esm({
|
|
|
37927
38486
|
});
|
|
37928
38487
|
}
|
|
37929
38488
|
/**
|
|
37930
|
-
* Respond with OAuth error - redirect if possible, otherwise show error page
|
|
38489
|
+
* Respond with OAuth error - redirect if possible, otherwise show error page.
|
|
38490
|
+
*
|
|
38491
|
+
* @param redirectUri A redirect_uri validated for the client; omit it for an error page.
|
|
38492
|
+
* @param error The RFC 6749 §4.1.2.1 error code. Default `invalid_request`.
|
|
37931
38493
|
*/
|
|
37932
|
-
respondWithError(errors, redirectUri, state) {
|
|
38494
|
+
respondWithError(errors, redirectUri, state, error = "invalid_request") {
|
|
37933
38495
|
const errorDescription = errors.join("; ");
|
|
37934
38496
|
if (redirectUri) {
|
|
37935
38497
|
const safe = safeRedirectUriSchema.safeParse(redirectUri);
|
|
37936
38498
|
if (safe.success) {
|
|
37937
38499
|
const url = new URL(safe.data);
|
|
37938
|
-
url.searchParams.set("error",
|
|
38500
|
+
url.searchParams.set("error", error);
|
|
37939
38501
|
url.searchParams.set("error_description", errorDescription);
|
|
37940
38502
|
if (state) {
|
|
37941
38503
|
url.searchParams.set("state", state);
|
|
37942
38504
|
}
|
|
38505
|
+
const issuer = this.resolveIssuer(this.rawInput.request);
|
|
38506
|
+
if (issuer) url.searchParams.set("iss", issuer);
|
|
37943
38507
|
this.respond(httpRespond.redirect(url.toString()));
|
|
37944
38508
|
return;
|
|
37945
38509
|
}
|
|
37946
38510
|
}
|
|
37947
|
-
this.respond(this.htmlPage(this.renderErrorPage(
|
|
38511
|
+
this.respond(this.htmlPage(this.renderErrorPage(error, errorDescription), 400));
|
|
37948
38512
|
}
|
|
37949
38513
|
/**
|
|
37950
38514
|
* Render the local login page.
|
|
@@ -38223,9 +38787,13 @@ var init_oauth_callback_flow = __esm({
|
|
|
38223
38787
|
* Returns `undefined` when the configured auth instance has no issuer, so the
|
|
38224
38788
|
* parameter is simply omitted rather than emitted empty — a client validating
|
|
38225
38789
|
* `iss` treats absence as "not supported" and proceeds.
|
|
38790
|
+
*
|
|
38791
|
+
* With `request`, the issuer the tokens redeemed for this request name
|
|
38792
|
+
* (`LocalPrimaryAuth.issuerFor`); without it, the boot-time issuer.
|
|
38226
38793
|
*/
|
|
38227
|
-
resolveIssuer() {
|
|
38228
|
-
const
|
|
38794
|
+
resolveIssuer(request) {
|
|
38795
|
+
const auth = this.scope.auth;
|
|
38796
|
+
const issuer = request && auth?.issuerFor ? auth.issuerFor(request) : auth?.issuer;
|
|
38229
38797
|
return typeof issuer === "string" && issuer.length > 0 ? issuer : void 0;
|
|
38230
38798
|
}
|
|
38231
38799
|
async checkIpFilter() {
|
|
@@ -38763,7 +39331,7 @@ var init_oauth_callback_flow = __esm({
|
|
|
38763
39331
|
if (originalState) {
|
|
38764
39332
|
url.searchParams.set("state", originalState);
|
|
38765
39333
|
}
|
|
38766
|
-
const issuer = this.resolveIssuer();
|
|
39334
|
+
const issuer = this.resolveIssuer(this.rawInput.request);
|
|
38767
39335
|
if (issuer) url.searchParams.set("iss", issuer);
|
|
38768
39336
|
if (isIncremental && targetAppId) {
|
|
38769
39337
|
url.searchParams.set("incremental", "true");
|
|
@@ -40094,7 +40662,7 @@ var init_oauth_provider_callback_flow = __esm({
|
|
|
40094
40662
|
if (session.state) {
|
|
40095
40663
|
url.searchParams.set("state", session.state);
|
|
40096
40664
|
}
|
|
40097
|
-
url.searchParams.set("iss", this.getLocalAuth().
|
|
40665
|
+
url.searchParams.set("iss", this.getLocalAuth().issuerFor(this.rawInput.request));
|
|
40098
40666
|
this.logger.info(
|
|
40099
40667
|
`Federated auth complete: ${selectedProviderIds.length} providers authenticated, redirecting to client`
|
|
40100
40668
|
);
|
|
@@ -40665,7 +41233,7 @@ var init_oauth_token_flow = __esm({
|
|
|
40665
41233
|
stateSchema20 = z70.object({
|
|
40666
41234
|
body: tokenRequestSchema.optional(),
|
|
40667
41235
|
grantType: z70.enum(["authorization_code", "refresh_token", "anonymous"]).optional(),
|
|
40668
|
-
isDefaultAuthProvider: z70.boolean().describe("
|
|
41236
|
+
isDefaultAuthProvider: z70.boolean().describe("No sign-in to run: no auth options, or public mode (the same configuration, written out)"),
|
|
40669
41237
|
isOrchestrated: z70.boolean().describe("If auth mode is orchestrated"),
|
|
40670
41238
|
// Token response data
|
|
40671
41239
|
tokenResponse: z70.object({
|
|
@@ -40695,7 +41263,7 @@ var init_oauth_token_flow = __esm({
|
|
|
40695
41263
|
async parseInput() {
|
|
40696
41264
|
const { metadata } = this.scope;
|
|
40697
41265
|
const { request } = this.rawInput;
|
|
40698
|
-
const isDefaultAuthProvider = !metadata.auth;
|
|
41266
|
+
const isDefaultAuthProvider = !metadata.auth || isPublicMode(metadata.auth);
|
|
40699
41267
|
const isOrchestrated = !isDefaultAuthProvider;
|
|
40700
41268
|
const rawBody = coerceTokenRequestBody(request);
|
|
40701
41269
|
const basic = parseBasicAuthorization(request.headers?.["authorization"]);
|
|
@@ -40744,7 +41312,10 @@ var init_oauth_token_flow = __esm({
|
|
|
40744
41312
|
const authOptions = this.scope.auth?.options;
|
|
40745
41313
|
if (isDefaultAuthProvider && body.code === "anonymous" && !!authOptions && allowsPublicAccess(authOptions)) {
|
|
40746
41314
|
const localAuth2 = this.scope.auth;
|
|
40747
|
-
const accessToken = await localAuth2.signAnonymousJwt({
|
|
41315
|
+
const accessToken = await localAuth2.signAnonymousJwt({
|
|
41316
|
+
audience: this.canonicalResource(),
|
|
41317
|
+
issuer: localAuth2.issuerFor(this.rawInput.request)
|
|
41318
|
+
});
|
|
40748
41319
|
this.state.set("tokenResponse", {
|
|
40749
41320
|
access_token: accessToken,
|
|
40750
41321
|
token_type: "Bearer",
|
|
@@ -40760,7 +41331,8 @@ var init_oauth_token_flow = __esm({
|
|
|
40760
41331
|
body.redirect_uri,
|
|
40761
41332
|
body.code_verifier,
|
|
40762
41333
|
body.client_secret,
|
|
40763
|
-
this.canonicalResource()
|
|
41334
|
+
this.canonicalResource(),
|
|
41335
|
+
localAuth.issuerFor(this.rawInput.request)
|
|
40764
41336
|
);
|
|
40765
41337
|
if ("error" in result) {
|
|
40766
41338
|
this.logger.warn(`Code exchange failed: ${result.error}`);
|
|
@@ -40798,7 +41370,10 @@ var init_oauth_token_flow = __esm({
|
|
|
40798
41370
|
return;
|
|
40799
41371
|
}
|
|
40800
41372
|
const localAuth2 = this.scope.auth;
|
|
40801
|
-
const accessToken = await localAuth2.signAnonymousJwt({
|
|
41373
|
+
const accessToken = await localAuth2.signAnonymousJwt({
|
|
41374
|
+
audience: this.canonicalResource(),
|
|
41375
|
+
issuer: localAuth2.issuerFor(this.rawInput.request)
|
|
41376
|
+
});
|
|
40802
41377
|
this.state.set("tokenResponse", {
|
|
40803
41378
|
access_token: accessToken,
|
|
40804
41379
|
token_type: "Bearer",
|
|
@@ -40812,7 +41387,8 @@ var init_oauth_token_flow = __esm({
|
|
|
40812
41387
|
body.refresh_token,
|
|
40813
41388
|
body.client_id,
|
|
40814
41389
|
body.client_secret,
|
|
40815
|
-
this.canonicalResource()
|
|
41390
|
+
this.canonicalResource(),
|
|
41391
|
+
localAuth.issuerFor(this.rawInput.request)
|
|
40816
41392
|
);
|
|
40817
41393
|
if ("error" in result) {
|
|
40818
41394
|
this.logger.warn(`Refresh token failed: ${result.error}`);
|
|
@@ -40868,7 +41444,10 @@ var init_oauth_token_flow = __esm({
|
|
|
40868
41444
|
}
|
|
40869
41445
|
const localAuth = this.scope.auth;
|
|
40870
41446
|
const audience = body?.grant_type === "anonymous" && body.resource ? body.resource : canonicalResource;
|
|
40871
|
-
const accessToken = await localAuth.signAnonymousJwt({
|
|
41447
|
+
const accessToken = await localAuth.signAnonymousJwt({
|
|
41448
|
+
audience,
|
|
41449
|
+
issuer: localAuth.issuerFor(this.rawInput.request)
|
|
41450
|
+
});
|
|
40872
41451
|
this.state.set("tokenResponse", {
|
|
40873
41452
|
access_token: accessToken,
|
|
40874
41453
|
token_type: "Bearer",
|
|
@@ -40996,7 +41575,8 @@ var init_oauth_userinfo_flow = __esm({
|
|
|
40996
41575
|
return;
|
|
40997
41576
|
}
|
|
40998
41577
|
const localAuth = this.scope.auth;
|
|
40999
|
-
const
|
|
41578
|
+
const issuer = localAuth.acceptedIssuersFor?.(this.rawInput.request);
|
|
41579
|
+
const result = await localAuth.verifyGatewayToken(token, baseUrl, void 0, issuer);
|
|
41000
41580
|
if (!result.ok) {
|
|
41001
41581
|
this.logger.warn("userinfo: token verification failed", { error: result.error });
|
|
41002
41582
|
this.respond(
|
|
@@ -41054,6 +41634,7 @@ var init_oauth_userinfo_flow = __esm({
|
|
|
41054
41634
|
// libs/sdk/src/auth/flows/session.verify.flow.ts
|
|
41055
41635
|
import "reflect-metadata";
|
|
41056
41636
|
import {
|
|
41637
|
+
anonymousCallerClaims,
|
|
41057
41638
|
buildInsufficientScopeHeader,
|
|
41058
41639
|
buildInvalidTokenHeader,
|
|
41059
41640
|
buildUnauthorizedHeader,
|
|
@@ -41090,7 +41671,14 @@ var init_session_verify_flow = __esm({
|
|
|
41090
41671
|
init_common();
|
|
41091
41672
|
init_notification_service();
|
|
41092
41673
|
init_session_id_utils();
|
|
41093
|
-
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
|
+
});
|
|
41094
41682
|
stateSchema22 = z72.object({
|
|
41095
41683
|
baseUrl: z72.string().min(1),
|
|
41096
41684
|
authorizationHeader: z72.string().optional(),
|
|
@@ -41148,20 +41736,22 @@ var init_session_verify_flow = __esm({
|
|
|
41148
41736
|
* Encapsulates the shared logic for session creation, payload encryption, and user derivation.
|
|
41149
41737
|
*/
|
|
41150
41738
|
createAnonymousSession(options) {
|
|
41151
|
-
const { authMode,
|
|
41739
|
+
const { authMode, subject, sessionIdHeader } = options;
|
|
41152
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
|
+
}
|
|
41153
41748
|
const machineId = getMachineId2();
|
|
41154
41749
|
const authSignature = subject ? `${authMode}:${subject}` : authMode;
|
|
41155
41750
|
if (sessionIdHeader) {
|
|
41156
41751
|
const existingPayload = decryptPublicSession(sessionIdHeader, authSignature);
|
|
41157
41752
|
if (existingPayload && existingPayload.nodeId === machineId) {
|
|
41158
41753
|
const anonId = existingPayload.uuid ?? `${existingPayload.iat * 1e3}`;
|
|
41159
|
-
const user2 =
|
|
41160
|
-
sub: subject ?? `anon:${anonId}`,
|
|
41161
|
-
iss: issuer,
|
|
41162
|
-
name: subject ? "Static token" : "Anonymous",
|
|
41163
|
-
scope: scopes.join(" ")
|
|
41164
|
-
};
|
|
41754
|
+
const user2 = anonymousCallerClaims(options, anonId);
|
|
41165
41755
|
this.respond({
|
|
41166
41756
|
kind: "authorized",
|
|
41167
41757
|
authorization: {
|
|
@@ -41175,13 +41765,8 @@ var init_session_verify_flow = __esm({
|
|
|
41175
41765
|
this.logger.verbose("createAnonymousSession: ignoring unrecognized client session id; minting a fresh one");
|
|
41176
41766
|
}
|
|
41177
41767
|
const now = Date.now();
|
|
41178
|
-
const user = {
|
|
41179
|
-
sub: subject ?? `anon:${randomUUID20()}`,
|
|
41180
|
-
iss: issuer,
|
|
41181
|
-
name: subject ? "Static token" : "Anonymous",
|
|
41182
|
-
scope: scopes.join(" ")
|
|
41183
|
-
};
|
|
41184
41768
|
const uuid = randomUUID20();
|
|
41769
|
+
const user = anonymousCallerClaims(options, uuid);
|
|
41185
41770
|
const platformDetectionConfig = this.scope.metadata.transport?.platformDetection;
|
|
41186
41771
|
const platformType = detectPlatformFromUserAgent(this.state.userAgent, platformDetectionConfig);
|
|
41187
41772
|
const payload = {
|
|
@@ -41374,7 +41959,8 @@ var init_session_verify_flow = __esm({
|
|
|
41374
41959
|
} else {
|
|
41375
41960
|
const configuredAudience = isOrchestratedMode(authOptions) ? authOptions.expectedAudience : void 0;
|
|
41376
41961
|
const resource = computeResource(this.rawInput.request, this.scope.entryPath, this.scope.routeBase);
|
|
41377
|
-
|
|
41962
|
+
const issuer = auth.acceptedIssuersFor?.(this.rawInput.request);
|
|
41963
|
+
verify = auth.verifyGatewayToken(token, this.state.required.baseUrl, configuredAudience ?? resource, issuer);
|
|
41378
41964
|
}
|
|
41379
41965
|
const result = await verify;
|
|
41380
41966
|
if (result.ok) {
|
|
@@ -41621,6 +42207,7 @@ var init_well_known_jwks_flow = __esm({
|
|
|
41621
42207
|
|
|
41622
42208
|
// libs/sdk/src/auth/flows/well-known.oauth-authorization-server.flow.ts
|
|
41623
42209
|
import "reflect-metadata";
|
|
42210
|
+
import { advertisedScopes } from "@frontmcp/auth";
|
|
41624
42211
|
import { z as z74 } from "@frontmcp/lazy-zod";
|
|
41625
42212
|
import { isProduction as isProduction4 } from "@frontmcp/utils";
|
|
41626
42213
|
function resolveDcrEnabled(auth) {
|
|
@@ -41665,6 +42252,9 @@ var init_well_known_oauth_authorization_server_flow = __esm({
|
|
|
41665
42252
|
wellKnownAsStateSchema = z74.object({
|
|
41666
42253
|
baseUrl: z74.string().min(1),
|
|
41667
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(),
|
|
41668
42258
|
// Root origin (proto://host WITHOUT the entryPath prefix). The OAuth
|
|
41669
42259
|
// endpoints (/oauth/authorize, /oauth/token, /oauth/register, …) are
|
|
41670
42260
|
// registered at literal root paths by their flows, NOT under entryPath, so
|
|
@@ -41696,6 +42286,9 @@ var init_well_known_oauth_authorization_server_flow = __esm({
|
|
|
41696
42286
|
if (!request) throw new FlowInputMissingError("request", "well-known:oauth-authorization-server");
|
|
41697
42287
|
const { metadata } = this.scope;
|
|
41698
42288
|
const baseUrl = getRequestBaseUrl(request, this.scope.entryPath);
|
|
42289
|
+
const issuer = this.scope.auth?.issuerFor?.(
|
|
42290
|
+
request
|
|
42291
|
+
);
|
|
41699
42292
|
const oauthBaseUrl = getRequestBaseUrl(request);
|
|
41700
42293
|
let cimdEnabled = true;
|
|
41701
42294
|
if (metadata.auth && isOrchestratedMode(metadata.auth)) {
|
|
@@ -41704,8 +42297,11 @@ var init_well_known_oauth_authorization_server_flow = __esm({
|
|
|
41704
42297
|
this.state.set(
|
|
41705
42298
|
wellKnownAsStateSchema.parse({
|
|
41706
42299
|
baseUrl,
|
|
42300
|
+
issuer,
|
|
41707
42301
|
oauthBaseUrl,
|
|
41708
|
-
|
|
42302
|
+
// The scopes this server grants (`allowedScopes`, #262): the literal
|
|
42303
|
+
// entries; a `*` glob names no scope a client could ask for.
|
|
42304
|
+
scopesSupported: metadata.auth && isOrchestratedMode(metadata.auth) ? advertisedScopes(metadata.auth.allowedScopes) : [],
|
|
41709
42305
|
tokenEndpointAuthMethods: [],
|
|
41710
42306
|
// #462 — advertise registration_endpoint only when local-AS DCR is
|
|
41711
42307
|
// actually active (explicit auth.dcr.enabled, else dev/prod default).
|
|
@@ -41716,6 +42312,7 @@ var init_well_known_oauth_authorization_server_flow = __esm({
|
|
|
41716
42312
|
);
|
|
41717
42313
|
}
|
|
41718
42314
|
async collectData() {
|
|
42315
|
+
const { issuer } = this.state;
|
|
41719
42316
|
const {
|
|
41720
42317
|
baseUrl,
|
|
41721
42318
|
oauthBaseUrl,
|
|
@@ -41726,7 +42323,7 @@ var init_well_known_oauth_authorization_server_flow = __esm({
|
|
|
41726
42323
|
cimdEnabled
|
|
41727
42324
|
} = this.state.required;
|
|
41728
42325
|
if (isOrchestrated) {
|
|
41729
|
-
const baseIssuer =
|
|
42326
|
+
const baseIssuer = issuer ?? baseUrl;
|
|
41730
42327
|
this.respond({
|
|
41731
42328
|
kind: "json",
|
|
41732
42329
|
contentType: "application/json; charset=utf-8",
|
|
@@ -41753,6 +42350,8 @@ var init_well_known_oauth_authorization_server_flow = __esm({
|
|
|
41753
42350
|
grant_types_supported: ["authorization_code", "refresh_token"],
|
|
41754
42351
|
scopes_supported: scopesSupported,
|
|
41755
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,
|
|
41756
42355
|
// CIMD support advertisement per draft-ietf-oauth-client-id-metadata-document-00
|
|
41757
42356
|
client_id_metadata_document_supported: cimdEnabled
|
|
41758
42357
|
}
|
|
@@ -41789,6 +42388,7 @@ var init_well_known_oauth_authorization_server_flow = __esm({
|
|
|
41789
42388
|
|
|
41790
42389
|
// libs/sdk/src/auth/flows/well-known.prm.flow.ts
|
|
41791
42390
|
import "reflect-metadata";
|
|
42391
|
+
import { resourceScopesFor } from "@frontmcp/auth";
|
|
41792
42392
|
import { z as z75 } from "@frontmcp/lazy-zod";
|
|
41793
42393
|
var inputSchema26, stateSchema24, outputSchema24, plan24, name25, Stage25, WellKnownPrmFlow;
|
|
41794
42394
|
var init_well_known_prm_flow = __esm({
|
|
@@ -41800,14 +42400,16 @@ var init_well_known_prm_flow = __esm({
|
|
|
41800
42400
|
stateSchema24 = z75.object({
|
|
41801
42401
|
resource: z75.string().min(1),
|
|
41802
42402
|
baseUrl: z75.string().min(1),
|
|
41803
|
-
|
|
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()),
|
|
41804
42406
|
isOrchestrated: z75.boolean()
|
|
41805
42407
|
});
|
|
41806
42408
|
outputSchema24 = HttpJsonSchema.extend({
|
|
41807
42409
|
body: z75.object({
|
|
41808
42410
|
resource: z75.string().min(1),
|
|
41809
42411
|
authorization_servers: z75.array(z75.string().min(1)).min(1),
|
|
41810
|
-
scopes_supported: z75.array(z75.string()).
|
|
42412
|
+
scopes_supported: z75.array(z75.string()).optional(),
|
|
41811
42413
|
bearer_methods_supported: z75.array(z75.string()).default(["header"])
|
|
41812
42414
|
}).passthrough()
|
|
41813
42415
|
});
|
|
@@ -41831,11 +42433,17 @@ var init_well_known_prm_flow = __esm({
|
|
|
41831
42433
|
if (!request) throw new FlowInputMissingError("request", "well-known:prm");
|
|
41832
42434
|
const resource = computeResource(request, scope.entryPath, scope.routeBase);
|
|
41833
42435
|
const baseUrl = getRequestBaseUrl(request, scope.entryPath);
|
|
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()])];
|
|
41834
42441
|
this.state.set(
|
|
41835
42442
|
stateSchema24.parse({
|
|
41836
42443
|
resource,
|
|
41837
42444
|
baseUrl,
|
|
41838
|
-
|
|
42445
|
+
authorizationServer,
|
|
42446
|
+
scopesSupported,
|
|
41839
42447
|
isOrchestrated: false
|
|
41840
42448
|
//scope.orchestrated,// TODO: fix
|
|
41841
42449
|
})
|
|
@@ -41843,6 +42451,8 @@ var init_well_known_prm_flow = __esm({
|
|
|
41843
42451
|
}
|
|
41844
42452
|
async collectData() {
|
|
41845
42453
|
const { resource, baseUrl, scopesSupported, isOrchestrated } = this.state.required;
|
|
42454
|
+
const { authorizationServer } = this.state;
|
|
42455
|
+
const scopes = scopesSupported.length > 0 ? { scopes_supported: scopesSupported } : {};
|
|
41846
42456
|
if (isOrchestrated) {
|
|
41847
42457
|
this.respond({
|
|
41848
42458
|
kind: "json",
|
|
@@ -41854,8 +42464,8 @@ var init_well_known_prm_flow = __esm({
|
|
|
41854
42464
|
headers: { "cache-control": "no-store" },
|
|
41855
42465
|
body: {
|
|
41856
42466
|
resource,
|
|
41857
|
-
authorization_servers: [baseUrl],
|
|
41858
|
-
|
|
42467
|
+
authorization_servers: [authorizationServer ?? baseUrl],
|
|
42468
|
+
...scopes,
|
|
41859
42469
|
bearer_methods_supported: ["header"]
|
|
41860
42470
|
}
|
|
41861
42471
|
});
|
|
@@ -41868,8 +42478,8 @@ var init_well_known_prm_flow = __esm({
|
|
|
41868
42478
|
headers: { "cache-control": "no-store" },
|
|
41869
42479
|
body: {
|
|
41870
42480
|
resource,
|
|
41871
|
-
authorization_servers: [baseUrl],
|
|
41872
|
-
|
|
42481
|
+
authorization_servers: [authorizationServer ?? baseUrl],
|
|
42482
|
+
...scopes,
|
|
41873
42483
|
bearer_methods_supported: ["header"]
|
|
41874
42484
|
}
|
|
41875
42485
|
});
|
|
@@ -41899,6 +42509,24 @@ var init_well_known_prm_flow = __esm({
|
|
|
41899
42509
|
}
|
|
41900
42510
|
});
|
|
41901
42511
|
|
|
42512
|
+
// libs/sdk/src/auth/session-scope-identity.ts
|
|
42513
|
+
import { isAnonymousSubject as isAnonymousSubject3 } from "@frontmcp/auth";
|
|
42514
|
+
function sessionScopeIdentity(ctx) {
|
|
42515
|
+
const verified = ctx.verifiedSessionId;
|
|
42516
|
+
if (verified) return verified;
|
|
42517
|
+
const authInfo = ctx.authInfo ?? {};
|
|
42518
|
+
const extraUser = authInfo.extra?.["user"];
|
|
42519
|
+
const principal = [authInfo.user?.sub, extraUser?.sub, authInfo.clientId].find(
|
|
42520
|
+
(candidate) => !isAnonymousSubject3(candidate)
|
|
42521
|
+
);
|
|
42522
|
+
return principal ? `principal:${principal}` : void 0;
|
|
42523
|
+
}
|
|
42524
|
+
var init_session_scope_identity = __esm({
|
|
42525
|
+
"libs/sdk/src/auth/session-scope-identity.ts"() {
|
|
42526
|
+
"use strict";
|
|
42527
|
+
}
|
|
42528
|
+
});
|
|
42529
|
+
|
|
41902
42530
|
// libs/sdk/src/auth/secure-store/secure-store.providers.ts
|
|
41903
42531
|
import {
|
|
41904
42532
|
SECURE_STORE_ACCESSOR,
|
|
@@ -41906,8 +42534,9 @@ import {
|
|
|
41906
42534
|
} from "@frontmcp/auth";
|
|
41907
42535
|
import { ProviderScope as ProviderScope6 } from "@frontmcp/di";
|
|
41908
42536
|
function resolveRequestSessionId(ctx) {
|
|
41909
|
-
const
|
|
41910
|
-
|
|
42537
|
+
const identity = sessionScopeIdentity(ctx);
|
|
42538
|
+
if (identity) return identity;
|
|
42539
|
+
throw new SessionIdentityRequiredError("The secure store's session scope");
|
|
41911
42540
|
}
|
|
41912
42541
|
function createSecureStoreProviders(opts) {
|
|
41913
42542
|
const { backend, scope, ttlMs } = opts;
|
|
@@ -41939,7 +42568,9 @@ var init_secure_store_providers = __esm({
|
|
|
41939
42568
|
"use strict";
|
|
41940
42569
|
init_common();
|
|
41941
42570
|
init_frontmcp_context_provider();
|
|
42571
|
+
init_session_identity_required_error();
|
|
41942
42572
|
init_credentials_providers();
|
|
42573
|
+
init_session_scope_identity();
|
|
41943
42574
|
SECURE_STORE_BACKEND = /* @__PURE__ */ Symbol.for("frontmcp:SECURE_STORE_BACKEND");
|
|
41944
42575
|
}
|
|
41945
42576
|
});
|
|
@@ -42027,6 +42658,14 @@ function audienceMatchesResource(aud, expected) {
|
|
|
42027
42658
|
function isUnsupportedOperation(error) {
|
|
42028
42659
|
return error instanceof StorageNotSupportedError;
|
|
42029
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
|
+
}
|
|
42030
42669
|
var MIN_HS256_SECRET_BYTES, defaultNoAuthSecret, RESERVED_JWT_CLAIMS, LocalPrimaryAuth;
|
|
42031
42670
|
var init_instance_local_primary_auth = __esm({
|
|
42032
42671
|
"libs/sdk/src/auth/instances/instance.local-primary-auth.ts"() {
|
|
@@ -42079,6 +42718,7 @@ var init_instance_local_primary_auth = __esm({
|
|
|
42079
42718
|
this.port = this.providers.getActiveScope().metadata.http?.port ?? 3001;
|
|
42080
42719
|
this.host = getEnv4("FRONTMCP_PUBLIC_HOST")?.trim() || "localhost";
|
|
42081
42720
|
this.issuer = this.deriveIssuer(options);
|
|
42721
|
+
this.publicHostPinned = !!getEnv4("FRONTMCP_PUBLIC_HOST")?.trim();
|
|
42082
42722
|
const jwtSecret = getEnv4("JWT_SECRET");
|
|
42083
42723
|
const hasJwtSecret = !!jwtSecret?.trim();
|
|
42084
42724
|
if (hasJwtSecret && jwtSecret) {
|
|
@@ -42117,7 +42757,10 @@ var init_instance_local_primary_auth = __esm({
|
|
|
42117
42757
|
providers;
|
|
42118
42758
|
host;
|
|
42119
42759
|
port;
|
|
42760
|
+
/** The boot-time issuer: the configured one, else this server's listener address (see {@link issuerFor}). */
|
|
42120
42761
|
issuer;
|
|
42762
|
+
/** Whether `FRONTMCP_PUBLIC_HOST` names the boot-time issuer's host, pinning it for every request. */
|
|
42763
|
+
publicHostPinned;
|
|
42121
42764
|
keys = [];
|
|
42122
42765
|
secret;
|
|
42123
42766
|
logger;
|
|
@@ -42266,18 +42909,61 @@ var init_instance_local_primary_auth = __esm({
|
|
|
42266
42909
|
* supported knob for aligning the issuer with discovery.
|
|
42267
42910
|
*/
|
|
42268
42911
|
deriveIssuer(options) {
|
|
42269
|
-
|
|
42270
|
-
|
|
42271
|
-
|
|
42272
|
-
|
|
42273
|
-
if (
|
|
42274
|
-
|
|
42275
|
-
|
|
42276
|
-
|
|
42277
|
-
|
|
42278
|
-
|
|
42912
|
+
return this.configuredIssuer(options) ?? `http://${this.host}:${this.port}${this.scope.fullPath}`;
|
|
42913
|
+
}
|
|
42914
|
+
/** The issuer the options name: `issuer` in public mode, `local.issuer` in local and remote mode. */
|
|
42915
|
+
configuredIssuer(options) {
|
|
42916
|
+
if (isPublicMode(options)) return options.issuer;
|
|
42917
|
+
if (isOrchestratedMode(options)) return options.local?.issuer;
|
|
42918
|
+
return void 0;
|
|
42919
|
+
}
|
|
42920
|
+
/**
|
|
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:
|
|
42929
|
+
*
|
|
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.
|
|
42936
|
+
*/
|
|
42937
|
+
issuerFor(request) {
|
|
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);
|
|
42957
|
+
const options = this.options;
|
|
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}`);
|
|
42279
42965
|
}
|
|
42280
|
-
return
|
|
42966
|
+
return [...accepted];
|
|
42281
42967
|
}
|
|
42282
42968
|
/**
|
|
42283
42969
|
* Read the `tokenStorage` selection off the auth options. Only
|
|
@@ -42508,9 +43194,10 @@ var init_instance_local_primary_auth = __esm({
|
|
|
42508
43194
|
* user, and its scopes are the configured `anonymousScopes`, never a role.
|
|
42509
43195
|
*
|
|
42510
43196
|
* @param options.audience The protected resource the token is for (`aud`, #269).
|
|
43197
|
+
* @param options.issuer The issuer to name (`iss`): {@link issuerFor} the request. Default: the boot-time issuer.
|
|
42511
43198
|
*/
|
|
42512
43199
|
async signAnonymousJwt(options = {}) {
|
|
42513
|
-
const jwt = new SignJWT({ sub: `anon:${randomUUID21()}`, anonymous: true, scope: this.anonymousScopes().join(" ") }).setProtectedHeader({ alg: "HS256", typ: "JWT" }).setIssuedAt().setIssuer(this.issuer).setExpirationTime("1d").setJti(randomUUID21());
|
|
43200
|
+
const jwt = new SignJWT({ sub: `anon:${randomUUID21()}`, anonymous: true, scope: this.anonymousScopes().join(" ") }).setProtectedHeader({ alg: "HS256", typ: "JWT" }).setIssuedAt().setIssuer(options.issuer ?? this.issuer).setExpirationTime("1d").setJti(randomUUID21());
|
|
42514
43201
|
if (options.audience) jwt.setAudience(options.audience);
|
|
42515
43202
|
return jwt.sign(this.secret);
|
|
42516
43203
|
}
|
|
@@ -42527,8 +43214,10 @@ var init_instance_local_primary_auth = __esm({
|
|
|
42527
43214
|
*
|
|
42528
43215
|
* The secret alone doesn't make a token this server's: every server started
|
|
42529
43216
|
* with the same JWT_SECRET holds it. So a token must also (#269):
|
|
42530
|
-
* - name THIS instance as its issuer (`iss
|
|
42531
|
-
*
|
|
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
|
|
42532
43221
|
* - when `expectedAudience` is given, be issued for that protected resource,
|
|
42533
43222
|
* or one of those resources (`aud`, compared as RFC 8707 resource URIs).
|
|
42534
43223
|
* `session:verify` passes the request's resource URL, or the configured
|
|
@@ -42537,11 +43226,11 @@ var init_instance_local_primary_auth = __esm({
|
|
|
42537
43226
|
* Lifetime: `exp` is required (#272) and checked with `nbf` by `jose`. The
|
|
42538
43227
|
* algorithm is pinned to HS256 to block `alg` confusion.
|
|
42539
43228
|
*/
|
|
42540
|
-
async verifyGatewayToken(token, requestBaseUrl, expectedAudience) {
|
|
43229
|
+
async verifyGatewayToken(token, requestBaseUrl, expectedAudience, expectedIssuer) {
|
|
42541
43230
|
try {
|
|
42542
43231
|
const { payload, protectedHeader } = await jwtVerify(token, this.secret, {
|
|
42543
43232
|
algorithms: ["HS256"],
|
|
42544
|
-
issuer: this.issuer,
|
|
43233
|
+
issuer: typeof expectedIssuer === "string" ? expectedIssuer : expectedIssuer ? [...expectedIssuer] : this.issuer,
|
|
42545
43234
|
requiredClaims: ["exp"]
|
|
42546
43235
|
});
|
|
42547
43236
|
if (expectedAudience !== void 0 && !audienceMatchesResource(payload.aud, expectedAudience)) {
|
|
@@ -42562,7 +43251,7 @@ var init_instance_local_primary_auth = __esm({
|
|
|
42562
43251
|
/**
|
|
42563
43252
|
* Sign an access token for an authenticated user
|
|
42564
43253
|
*/
|
|
42565
|
-
async signAccessToken(user, scopes, audience, consentMetadata) {
|
|
43254
|
+
async signAccessToken(user, scopes, audience, consentMetadata, issuer) {
|
|
42566
43255
|
const claims = {
|
|
42567
43256
|
sub: user.sub,
|
|
42568
43257
|
scope: scopes.join(" ")
|
|
@@ -42598,7 +43287,7 @@ var init_instance_local_primary_auth = __esm({
|
|
|
42598
43287
|
}
|
|
42599
43288
|
}
|
|
42600
43289
|
}
|
|
42601
|
-
const jwt = new SignJWT(claims).setProtectedHeader({ alg: "HS256", typ: "JWT" }).setIssuedAt().setIssuer(this.issuer).setExpirationTime(`${this.accessTokenTtlSeconds}s`).setJti(randomUUID21());
|
|
43290
|
+
const jwt = new SignJWT(claims).setProtectedHeader({ alg: "HS256", typ: "JWT" }).setIssuedAt().setIssuer(issuer ?? this.issuer).setExpirationTime(`${this.accessTokenTtlSeconds}s`).setJti(randomUUID21());
|
|
42602
43291
|
if (audience) {
|
|
42603
43292
|
jwt.setAudience(audience);
|
|
42604
43293
|
}
|
|
@@ -42607,7 +43296,7 @@ var init_instance_local_primary_auth = __esm({
|
|
|
42607
43296
|
/**
|
|
42608
43297
|
* Exchange an authorization code for tokens
|
|
42609
43298
|
*/
|
|
42610
|
-
async exchangeCode(code, clientId, redirectUri, codeVerifier, clientSecret, defaultAudience) {
|
|
43299
|
+
async exchangeCode(code, clientId, redirectUri, codeVerifier, clientSecret, defaultAudience, issuer) {
|
|
42611
43300
|
if (this.dcrClientRegistryImpl.verifyClientSecret(clientId, clientSecret) === "invalid") {
|
|
42612
43301
|
this.logger.warn("Client authentication failed at token endpoint (authorization_code)");
|
|
42613
43302
|
return { error: "invalid_client", error_description: "Client authentication failed" };
|
|
@@ -42674,7 +43363,7 @@ var init_instance_local_primary_auth = __esm({
|
|
|
42674
43363
|
customClaims: codeRecord.customClaims
|
|
42675
43364
|
} : void 0;
|
|
42676
43365
|
const resource = codeRecord.resource ?? defaultAudience;
|
|
42677
|
-
const accessToken = await this.signAccessToken(user, codeRecord.scopes, resource, consentMetadata);
|
|
43366
|
+
const accessToken = await this.signAccessToken(user, codeRecord.scopes, resource, consentMetadata, issuer);
|
|
42678
43367
|
if (codeRecord.pendingAuthId && codeRecord.federatedLoginUsed) {
|
|
42679
43368
|
try {
|
|
42680
43369
|
const pendingAuthId = `pending:${codeRecord.pendingAuthId}`;
|
|
@@ -42716,7 +43405,7 @@ var init_instance_local_primary_auth = __esm({
|
|
|
42716
43405
|
/**
|
|
42717
43406
|
* Refresh an access token using a refresh token
|
|
42718
43407
|
*/
|
|
42719
|
-
async refreshAccessToken(refreshToken, clientId, clientSecret, defaultAudience) {
|
|
43408
|
+
async refreshAccessToken(refreshToken, clientId, clientSecret, defaultAudience, issuer) {
|
|
42720
43409
|
if (this.dcrClientRegistryImpl.verifyClientSecret(clientId, clientSecret) === "invalid") {
|
|
42721
43410
|
this.logger.warn("Client authentication failed at token endpoint (refresh_token)");
|
|
42722
43411
|
return { error: "invalid_client", error_description: "Client authentication failed" };
|
|
@@ -42753,7 +43442,7 @@ var init_instance_local_primary_auth = __esm({
|
|
|
42753
43442
|
customClaims: tokenRecord.customClaims
|
|
42754
43443
|
} : void 0;
|
|
42755
43444
|
const resource = tokenRecord.resource ?? defaultAudience;
|
|
42756
|
-
const accessToken = await this.signAccessToken(user, tokenRecord.scopes, resource, consentMetadata);
|
|
43445
|
+
const accessToken = await this.signAccessToken(user, tokenRecord.scopes, resource, consentMetadata, issuer);
|
|
42757
43446
|
const newRefreshRecord = this.authorizationStore.createRefreshTokenRecord({
|
|
42758
43447
|
clientId,
|
|
42759
43448
|
userSub: tokenRecord.userSub,
|
|
@@ -43497,6 +44186,111 @@ var init_auth_registry = __esm({
|
|
|
43497
44186
|
}
|
|
43498
44187
|
});
|
|
43499
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
|
+
|
|
43500
44294
|
// libs/sdk/src/channel/channel-notification.service.ts
|
|
43501
44295
|
var CHANNEL_NOTIFICATION_METHOD, ChannelNotificationService;
|
|
43502
44296
|
var init_channel_notification_service = __esm({
|
|
@@ -44466,6 +45260,12 @@ function wireJobCompletionSource(channel2, sourceConfig, jobEmitterSubscribe, lo
|
|
|
44466
45260
|
return;
|
|
44467
45261
|
}
|
|
44468
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
|
+
}
|
|
44469
45269
|
logger.verbose(`Job completion event for channel "${channel2.name}": job=${event.jobName}, status=${event.status}`);
|
|
44470
45270
|
channel2.handleEvent(event, event.sessionId).catch((err) => {
|
|
44471
45271
|
logger.error(`Failed to handle job completion event in channel "${channel2.name}"`, { error: err });
|
|
@@ -44478,6 +45278,41 @@ var init_job_completion_source = __esm({
|
|
|
44478
45278
|
}
|
|
44479
45279
|
});
|
|
44480
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
|
+
|
|
44481
45316
|
// libs/sdk/src/channel/channel-scope.helper.ts
|
|
44482
45317
|
async function registerChannelCapabilities(args) {
|
|
44483
45318
|
const {
|
|
@@ -44488,11 +45323,15 @@ async function registerChannelCapabilities(args) {
|
|
|
44488
45323
|
notificationService,
|
|
44489
45324
|
flowRegistry,
|
|
44490
45325
|
toolRegistry,
|
|
44491
|
-
|
|
44492
|
-
jobEmitterSubscribe,
|
|
45326
|
+
http: http2,
|
|
44493
45327
|
logger
|
|
44494
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));
|
|
44495
45332
|
const unsubscribers = [];
|
|
45333
|
+
const reservedPaths = http2 ? computeReservedPaths(http2.entryPath, http2.routeBase) : void 0;
|
|
45334
|
+
const webhookPaths = /* @__PURE__ */ new Map();
|
|
44496
45335
|
const channelRegistry = new ChannelRegistry(providers, channelsList, owner);
|
|
44497
45336
|
await channelRegistry.ready;
|
|
44498
45337
|
const channelNotificationService = new ChannelNotificationService(
|
|
@@ -44508,31 +45347,23 @@ async function registerChannelCapabilities(args) {
|
|
|
44508
45347
|
const sourceType = instance.source.type;
|
|
44509
45348
|
switch (sourceType) {
|
|
44510
45349
|
case "agent-completion": {
|
|
44511
|
-
|
|
44512
|
-
|
|
44513
|
-
|
|
44514
|
-
|
|
44515
|
-
|
|
44516
|
-
|
|
44517
|
-
|
|
44518
|
-
unsubscribers.push(unsub);
|
|
44519
|
-
} else {
|
|
44520
|
-
logger.warn(`Channel "${instance.name}" has agent-completion source but no agent emitter available`);
|
|
44521
|
-
}
|
|
45350
|
+
const unsub = wireAgentCompletionSource(
|
|
45351
|
+
instance,
|
|
45352
|
+
instance.metadata.source,
|
|
45353
|
+
agentEmitterSubscribe,
|
|
45354
|
+
logger
|
|
45355
|
+
);
|
|
45356
|
+
unsubscribers.push(unsub);
|
|
44522
45357
|
break;
|
|
44523
45358
|
}
|
|
44524
45359
|
case "job-completion": {
|
|
44525
|
-
|
|
44526
|
-
|
|
44527
|
-
|
|
44528
|
-
|
|
44529
|
-
|
|
44530
|
-
|
|
44531
|
-
|
|
44532
|
-
unsubscribers.push(unsub);
|
|
44533
|
-
} else {
|
|
44534
|
-
logger.warn(`Channel "${instance.name}" has job-completion source but no job emitter available`);
|
|
44535
|
-
}
|
|
45360
|
+
const unsub = wireJobCompletionSource(
|
|
45361
|
+
instance,
|
|
45362
|
+
instance.metadata.source,
|
|
45363
|
+
jobEmitterSubscribe,
|
|
45364
|
+
logger
|
|
45365
|
+
);
|
|
45366
|
+
unsubscribers.push(unsub);
|
|
44536
45367
|
break;
|
|
44537
45368
|
}
|
|
44538
45369
|
case "app-event": {
|
|
@@ -44541,7 +45372,25 @@ async function registerChannelCapabilities(args) {
|
|
|
44541
45372
|
unsubscribers.push(unsub);
|
|
44542
45373
|
break;
|
|
44543
45374
|
}
|
|
44544
|
-
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
|
+
}
|
|
44545
45394
|
case "manual":
|
|
44546
45395
|
break;
|
|
44547
45396
|
case "service":
|
|
@@ -44624,6 +45473,7 @@ async function registerChannelCapabilities(args) {
|
|
|
44624
45473
|
var init_channel_scope_helper = __esm({
|
|
44625
45474
|
"libs/sdk/src/channel/channel-scope.helper.ts"() {
|
|
44626
45475
|
"use strict";
|
|
45476
|
+
init_custom_routes_helper();
|
|
44627
45477
|
init_tool_instance();
|
|
44628
45478
|
init_tool_utils();
|
|
44629
45479
|
init_channel_notification_service();
|
|
@@ -44633,7 +45483,9 @@ var init_channel_scope_helper = __esm({
|
|
|
44633
45483
|
init_channel_reply_tool();
|
|
44634
45484
|
init_agent_completion_source();
|
|
44635
45485
|
init_app_event_source();
|
|
45486
|
+
init_completion_events();
|
|
44636
45487
|
init_job_completion_source();
|
|
45488
|
+
init_webhook_source();
|
|
44637
45489
|
}
|
|
44638
45490
|
});
|
|
44639
45491
|
|
|
@@ -45876,12 +46728,12 @@ async function createElicitationStore(options = {}) {
|
|
|
45876
46728
|
sqlite,
|
|
45877
46729
|
keyPrefix = "mcp:elicit:",
|
|
45878
46730
|
logger,
|
|
45879
|
-
isEdgeRuntime:
|
|
46731
|
+
isEdgeRuntime: isEdgeRuntime3 = false,
|
|
45880
46732
|
requiresPubSub = true,
|
|
45881
46733
|
encryption
|
|
45882
46734
|
} = options;
|
|
45883
46735
|
if (sqlite) {
|
|
45884
|
-
if (
|
|
46736
|
+
if (isEdgeRuntime3) {
|
|
45885
46737
|
throw new ElicitationNotSupportedError(
|
|
45886
46738
|
"SQLite elicitation store is not supported on Edge runtime (better-sqlite3 is a native module). Use Redis or Upstash instead."
|
|
45887
46739
|
);
|
|
@@ -45955,7 +46807,7 @@ async function createElicitationStore(options = {}) {
|
|
|
45955
46807
|
"Vercel KV is not supported for elicitation stores. Elicitation requires pub/sub for cross-node result routing, which Vercel KV does not support. Use Redis or Upstash instead."
|
|
45956
46808
|
);
|
|
45957
46809
|
}
|
|
45958
|
-
if (
|
|
46810
|
+
if (isEdgeRuntime3 && (!finalStorageConfig || finalStorageConfig.type === "memory")) {
|
|
45959
46811
|
throw new ElicitationNotSupportedError(
|
|
45960
46812
|
"Elicitation requires distributed storage when running on Edge runtime. Edge functions are stateless and cannot use in-memory elicitation. Configure Redis or Upstash storage."
|
|
45961
46813
|
);
|
|
@@ -45963,7 +46815,7 @@ async function createElicitationStore(options = {}) {
|
|
|
45963
46815
|
const storage = await createStorage2({
|
|
45964
46816
|
...finalStorageConfig,
|
|
45965
46817
|
prefix: keyPrefix,
|
|
45966
|
-
fallback:
|
|
46818
|
+
fallback: isEdgeRuntime3 ? "error" : "memory"
|
|
45967
46819
|
});
|
|
45968
46820
|
if (requiresPubSub && !storage.supportsPubSub()) {
|
|
45969
46821
|
throw new ElicitationNotSupportedError(
|
|
@@ -47629,6 +48481,7 @@ var WorkflowStepExecutor;
|
|
|
47629
48481
|
var init_workflow_step_executor = __esm({
|
|
47630
48482
|
"libs/sdk/src/workflow/engine/workflow-step.executor.ts"() {
|
|
47631
48483
|
"use strict";
|
|
48484
|
+
init_call_surface();
|
|
47632
48485
|
init_errors();
|
|
47633
48486
|
init_job_errors();
|
|
47634
48487
|
init_workflow_errors();
|
|
@@ -47691,7 +48544,7 @@ var init_workflow_step_executor = __esm({
|
|
|
47691
48544
|
const timer = setTimeout(() => {
|
|
47692
48545
|
reject(new WorkflowJobTimeoutError(job.name, timeout));
|
|
47693
48546
|
}, timeout);
|
|
47694
|
-
|
|
48547
|
+
runOnSurface("job", async () => ctx.execute(parsedInput)).then((result) => {
|
|
47695
48548
|
clearTimeout(timer);
|
|
47696
48549
|
resolve(result);
|
|
47697
48550
|
}).catch((err) => {
|
|
@@ -47705,6 +48558,7 @@ var init_workflow_step_executor = __esm({
|
|
|
47705
48558
|
});
|
|
47706
48559
|
|
|
47707
48560
|
// libs/sdk/src/workflow/engine/workflow.engine.ts
|
|
48561
|
+
import { getAsyncContextMode } from "@frontmcp/utils";
|
|
47708
48562
|
var WorkflowEngine;
|
|
47709
48563
|
var init_workflow_engine = __esm({
|
|
47710
48564
|
"libs/sdk/src/workflow/engine/workflow.engine.ts"() {
|
|
@@ -47774,7 +48628,7 @@ var init_workflow_engine = __esm({
|
|
|
47774
48628
|
}
|
|
47775
48629
|
continue;
|
|
47776
48630
|
}
|
|
47777
|
-
const batch = ready.slice(0, this.maxConcurrency);
|
|
48631
|
+
const batch = ready.slice(0, getAsyncContextMode() === "serialized" ? 1 : this.maxConcurrency);
|
|
47778
48632
|
await Promise.allSettled(
|
|
47779
48633
|
batch.map(async (step) => {
|
|
47780
48634
|
if (step.condition) {
|
|
@@ -47881,12 +48735,13 @@ var init_workflow_engine = __esm({
|
|
|
47881
48735
|
});
|
|
47882
48736
|
|
|
47883
48737
|
// libs/sdk/src/job/execution/job-execution.manager.ts
|
|
47884
|
-
import { randomUUID as randomUUID23 } from "@frontmcp/utils";
|
|
48738
|
+
import { randomUUID as randomUUID23, runRequestExclusive as runRequestExclusive4 } from "@frontmcp/utils";
|
|
47885
48739
|
var JobExecutionManager;
|
|
47886
48740
|
var init_job_execution_manager = __esm({
|
|
47887
48741
|
"libs/sdk/src/job/execution/job-execution.manager.ts"() {
|
|
47888
48742
|
"use strict";
|
|
47889
48743
|
init_principal_utils();
|
|
48744
|
+
init_call_surface();
|
|
47890
48745
|
init_errors();
|
|
47891
48746
|
init_workflow_engine();
|
|
47892
48747
|
init_job_permission_guard();
|
|
@@ -47923,7 +48778,7 @@ var init_job_execution_manager = __esm({
|
|
|
47923
48778
|
};
|
|
47924
48779
|
await this.stateStore.createRun(runRecord);
|
|
47925
48780
|
if (opts.background) {
|
|
47926
|
-
this.executeJobBackground(job, input, runId, opts).catch(async (err) => {
|
|
48781
|
+
runRequestExclusive4(() => this.executeJobBackground(job, input, runId, opts)).catch(async (err) => {
|
|
47927
48782
|
this.logger.error(`Background job execution failed: ${err}`);
|
|
47928
48783
|
try {
|
|
47929
48784
|
await this.updateState(runId, {
|
|
@@ -47963,18 +48818,20 @@ var init_job_execution_manager = __esm({
|
|
|
47963
48818
|
};
|
|
47964
48819
|
await this.stateStore.createRun(runRecord);
|
|
47965
48820
|
if (opts.background) {
|
|
47966
|
-
this.executeWorkflowBackground(workflow, jobRegistry, runId, opts).catch(
|
|
47967
|
-
|
|
47968
|
-
|
|
47969
|
-
|
|
47970
|
-
|
|
47971
|
-
|
|
47972
|
-
|
|
47973
|
-
|
|
47974
|
-
|
|
47975
|
-
|
|
48821
|
+
runRequestExclusive4(() => this.executeWorkflowBackground(workflow, jobRegistry, runId, opts)).catch(
|
|
48822
|
+
async (err) => {
|
|
48823
|
+
this.logger.error(`Background workflow execution failed: ${err}`);
|
|
48824
|
+
try {
|
|
48825
|
+
await this.updateState(runId, {
|
|
48826
|
+
state: "failed",
|
|
48827
|
+
error: { message: err?.message ?? String(err), name: err?.name ?? "Error" },
|
|
48828
|
+
completedAt: Date.now()
|
|
48829
|
+
});
|
|
48830
|
+
} catch (updateErr) {
|
|
48831
|
+
this.logger.error(`Failed to update run state after error: ${updateErr}`);
|
|
48832
|
+
}
|
|
47976
48833
|
}
|
|
47977
|
-
|
|
48834
|
+
);
|
|
47978
48835
|
return { runId, state: "pending" };
|
|
47979
48836
|
}
|
|
47980
48837
|
return this.executeWorkflowInline(workflow, jobRegistry, runId, opts);
|
|
@@ -48027,7 +48884,7 @@ var init_job_execution_manager = __esm({
|
|
|
48027
48884
|
authInfo: opts.authInfo ?? {},
|
|
48028
48885
|
contextProviders: opts.contextProviders
|
|
48029
48886
|
});
|
|
48030
|
-
const result = await ctx.execute(parsedInput);
|
|
48887
|
+
const result = await runOnSurface("job", async () => ctx.execute(parsedInput));
|
|
48031
48888
|
const logs = ctx.getLogs();
|
|
48032
48889
|
await this.updateState(runId, {
|
|
48033
48890
|
state: "completed",
|
|
@@ -48262,6 +49119,10 @@ var init_job_interface = __esm({
|
|
|
48262
49119
|
getLogs() {
|
|
48263
49120
|
return this._logs;
|
|
48264
49121
|
}
|
|
49122
|
+
/** A job (or a workflow step running it) calls tools on the `'job'` surface. */
|
|
49123
|
+
callToolSurface() {
|
|
49124
|
+
return "job";
|
|
49125
|
+
}
|
|
48265
49126
|
};
|
|
48266
49127
|
}
|
|
48267
49128
|
});
|
|
@@ -49364,7 +50225,11 @@ async function registerJobCapabilities(args) {
|
|
|
49364
50225
|
for (const dynWf of dynamicWorkflows) {
|
|
49365
50226
|
workflowRegistry.registerDynamic(dynWf);
|
|
49366
50227
|
}
|
|
49367
|
-
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
|
+
});
|
|
49368
50233
|
const managementTools = [
|
|
49369
50234
|
ListJobsTool,
|
|
49370
50235
|
ExecuteJobTool,
|
|
@@ -49393,9 +50258,27 @@ async function registerJobCapabilities(args) {
|
|
|
49393
50258
|
managementTools
|
|
49394
50259
|
};
|
|
49395
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
|
+
}
|
|
49396
50278
|
var init_job_scope_helper = __esm({
|
|
49397
50279
|
"libs/sdk/src/job/job-scope.helper.ts"() {
|
|
49398
50280
|
"use strict";
|
|
50281
|
+
init_completion_events();
|
|
49399
50282
|
init_execute_workflow_tool();
|
|
49400
50283
|
init_get_workflow_status_tool();
|
|
49401
50284
|
init_list_workflows_tool();
|
|
@@ -49620,111 +50503,6 @@ var init_enforced_metadata_check = __esm({
|
|
|
49620
50503
|
}
|
|
49621
50504
|
});
|
|
49622
50505
|
|
|
49623
|
-
// libs/sdk/src/server/custom-routes.helper.ts
|
|
49624
|
-
function computeReservedPaths(entryPath, routeBase) {
|
|
49625
|
-
const prefix = normalizeEntryPrefix(entryPath);
|
|
49626
|
-
const base = normalizeScopeBase(routeBase);
|
|
49627
|
-
const mcpBase = `${prefix}${base}`;
|
|
49628
|
-
const exact = /* @__PURE__ */ new Set([
|
|
49629
|
-
mcpBase === "" ? "/" : mcpBase,
|
|
49630
|
-
`${mcpBase}/sse`,
|
|
49631
|
-
`${mcpBase}/message`,
|
|
49632
|
-
"/health",
|
|
49633
|
-
"/metrics"
|
|
49634
|
-
]);
|
|
49635
|
-
return { exact, prefixes: ["/oauth", "/.well-known"] };
|
|
49636
|
-
}
|
|
49637
|
-
function normalizeForCompare(path3) {
|
|
49638
|
-
if (path3.length > 1 && path3.endsWith("/")) return path3.slice(0, -1);
|
|
49639
|
-
return path3;
|
|
49640
|
-
}
|
|
49641
|
-
function assertNotReserved(method, path3, reserved) {
|
|
49642
|
-
const normalized = normalizeForCompare(path3);
|
|
49643
|
-
if (reserved.exact.has(normalized)) {
|
|
49644
|
-
throw new ReservedRouteCollisionError(method, path3, normalized);
|
|
49645
|
-
}
|
|
49646
|
-
for (const prefix of reserved.prefixes) {
|
|
49647
|
-
if (normalized === prefix || normalized.startsWith(`${prefix}/`)) {
|
|
49648
|
-
throw new ReservedRouteCollisionError(method, path3, prefix);
|
|
49649
|
-
}
|
|
49650
|
-
}
|
|
49651
|
-
}
|
|
49652
|
-
function wrapWithAuth(handler, verifySession, logger) {
|
|
49653
|
-
return async (req, res, next) => {
|
|
49654
|
-
let result;
|
|
49655
|
-
try {
|
|
49656
|
-
result = await verifySession(req);
|
|
49657
|
-
} catch (error) {
|
|
49658
|
-
if (error instanceof FlowControl && error.type === "respond") {
|
|
49659
|
-
result = error.output;
|
|
49660
|
-
} else {
|
|
49661
|
-
throw error;
|
|
49662
|
-
}
|
|
49663
|
-
}
|
|
49664
|
-
if (!result || result.kind === "unauthorized") {
|
|
49665
|
-
logger.verbose("custom-route auth: unauthorized");
|
|
49666
|
-
return writeHttpResponse(
|
|
49667
|
-
res,
|
|
49668
|
-
httpRespond.unauthorized({
|
|
49669
|
-
headers: result?.kind === "unauthorized" ? { "WWW-Authenticate": result.prmMetadataHeader } : void 0
|
|
49670
|
-
})
|
|
49671
|
-
);
|
|
49672
|
-
}
|
|
49673
|
-
if (result.kind === "forbidden") {
|
|
49674
|
-
logger.verbose("custom-route auth: forbidden (insufficient scope)");
|
|
49675
|
-
return writeHttpResponse(
|
|
49676
|
-
res,
|
|
49677
|
-
httpRespond.forbidden({
|
|
49678
|
-
headers: { "WWW-Authenticate": result.prmMetadataHeader }
|
|
49679
|
-
})
|
|
49680
|
-
);
|
|
49681
|
-
}
|
|
49682
|
-
req.authSession = result.authorization;
|
|
49683
|
-
req[ServerRequestTokens.auth] = result.authorization;
|
|
49684
|
-
return handler(req, res, next);
|
|
49685
|
-
};
|
|
49686
|
-
}
|
|
49687
|
-
function wrapWithIpFilter(handler, checkClientIp) {
|
|
49688
|
-
return async (req, res, next) => {
|
|
49689
|
-
const rejection = await checkClientIp(req, res);
|
|
49690
|
-
if (rejection) return writeHttpResponse(res, rejection);
|
|
49691
|
-
return handler(req, res, next);
|
|
49692
|
-
};
|
|
49693
|
-
}
|
|
49694
|
-
function registerCustomHttpRoutes(args) {
|
|
49695
|
-
const { routes, server, verifySession, checkClientIp, entryPath, routeBase, logger } = args;
|
|
49696
|
-
if (!routes || routes.length === 0) return;
|
|
49697
|
-
const reserved = computeReservedPaths(entryPath, routeBase);
|
|
49698
|
-
for (const route of routes) {
|
|
49699
|
-
assertNotReserved(route.method, route.path, reserved);
|
|
49700
|
-
const handler = route.auth === true ? wrapWithAuth(route.handler, verifySession, logger) : route.handler;
|
|
49701
|
-
server.registerRoute(route.method, route.path, wrapWithIpFilter(handler, checkClientIp));
|
|
49702
|
-
logger.info(`Registered custom http route: ${route.method} ${route.path}${route.auth ? " (auth)" : ""}`);
|
|
49703
|
-
}
|
|
49704
|
-
}
|
|
49705
|
-
var ReservedRouteCollisionError;
|
|
49706
|
-
var init_custom_routes_helper = __esm({
|
|
49707
|
-
"libs/sdk/src/server/custom-routes.helper.ts"() {
|
|
49708
|
-
"use strict";
|
|
49709
|
-
init_common();
|
|
49710
|
-
init_server_validation();
|
|
49711
|
-
ReservedRouteCollisionError = class extends Error {
|
|
49712
|
-
constructor(method, path3, reserved) {
|
|
49713
|
-
super(
|
|
49714
|
-
`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.`
|
|
49715
|
-
);
|
|
49716
|
-
this.method = method;
|
|
49717
|
-
this.path = path3;
|
|
49718
|
-
this.reserved = reserved;
|
|
49719
|
-
this.name = "ReservedRouteCollisionError";
|
|
49720
|
-
}
|
|
49721
|
-
method;
|
|
49722
|
-
path;
|
|
49723
|
-
reserved;
|
|
49724
|
-
};
|
|
49725
|
-
}
|
|
49726
|
-
});
|
|
49727
|
-
|
|
49728
50506
|
// libs/sdk/src/skill/errors/tool-not-allowed.error.ts
|
|
49729
50507
|
var ToolNotAllowedError, ToolApprovalRequiredError;
|
|
49730
50508
|
var init_tool_not_allowed_error = __esm({
|
|
@@ -50899,7 +51677,7 @@ var init_load_skill_flow = __esm({
|
|
|
50899
51677
|
}
|
|
50900
51678
|
await assertSkillAuthorized(this.scope, entry, authInfo);
|
|
50901
51679
|
}
|
|
50902
|
-
loadResults.push({ loadResult: result });
|
|
51680
|
+
loadResults.push({ loadResult: skillToolsForCaller(result, this.scope.tools, ctx) });
|
|
50903
51681
|
}
|
|
50904
51682
|
this.state.set({ loadResults, warnings });
|
|
50905
51683
|
this.logger.verbose("loadSkills:done", { loaded: loadResults.length, notFound: warnings.length });
|
|
@@ -51145,7 +51923,10 @@ async function authorizeSkillHttpRequest(scope, skillsConfig, request, logger) {
|
|
|
51145
51923
|
if (mode === "inherit") {
|
|
51146
51924
|
const authOptions = scope.auth?.options;
|
|
51147
51925
|
if (!authOptions || isPublicMode(authOptions)) return { allowed: true, authInfo: {} };
|
|
51148
|
-
const verified = await scope.runFlow("session:verify", {
|
|
51926
|
+
const verified = await scope.runFlow("session:verify", {
|
|
51927
|
+
request,
|
|
51928
|
+
sessionless: true
|
|
51929
|
+
});
|
|
51149
51930
|
if (verified?.kind === "authorized") {
|
|
51150
51931
|
return { allowed: true, authInfo: { ...authInfoFromAuthorization(verified.authorization) } };
|
|
51151
51932
|
}
|
|
@@ -52114,7 +52895,12 @@ var init_skills_api_flow = __esm({
|
|
|
52114
52895
|
respondNotFound();
|
|
52115
52896
|
return;
|
|
52116
52897
|
}
|
|
52117
|
-
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
|
+
);
|
|
52118
52904
|
const skillEntry = createSkillEntryResolver(skillRegistry)(skillId, skill.id);
|
|
52119
52905
|
if (skillEntry) {
|
|
52120
52906
|
if (!await isSkillServable(this.scope, skillEntry, void 0, SKILLS_HTTP_SURFACE)) {
|
|
@@ -52401,6 +53187,8 @@ var init_server_interface = __esm({
|
|
|
52401
53187
|
ServerRequest = class extends IncomingMessage {
|
|
52402
53188
|
path;
|
|
52403
53189
|
query;
|
|
53190
|
+
/** Path parameters of the matched route (`:providerId` in `/oauth/provider/:providerId/callback`). */
|
|
53191
|
+
params;
|
|
52404
53192
|
body;
|
|
52405
53193
|
authSession;
|
|
52406
53194
|
};
|
|
@@ -52473,6 +53261,8 @@ var init_tool_interface = __esm({
|
|
|
52473
53261
|
_toolNameInternal;
|
|
52474
53262
|
/** @internal Tool input for fallback elicitation - set by CallToolFlow */
|
|
52475
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;
|
|
52476
53266
|
// ---- INPUT storages (backing fields)
|
|
52477
53267
|
_rawInput;
|
|
52478
53268
|
_input;
|
|
@@ -52505,6 +53295,10 @@ var init_tool_interface = __esm({
|
|
|
52505
53295
|
this._progressToken = progressToken;
|
|
52506
53296
|
this.signal = signal;
|
|
52507
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
|
+
}
|
|
52508
53302
|
/**
|
|
52509
53303
|
* Like {@link ExecutionContextBase.fetch}, and also aborted with {@link signal}, so a
|
|
52510
53304
|
* cancelled or timed-out call stops its outbound requests.
|
|
@@ -52923,9 +53717,10 @@ var init_agent_execution_loop = __esm({
|
|
|
52923
53717
|
let iterations = 0;
|
|
52924
53718
|
let totalPromptTokens = 0;
|
|
52925
53719
|
let totalCompletionTokens = 0;
|
|
53720
|
+
let timeout;
|
|
52926
53721
|
try {
|
|
52927
53722
|
const timeoutPromise = new Promise((_, reject) => {
|
|
52928
|
-
setTimeout(
|
|
53723
|
+
timeout = setTimeout(
|
|
52929
53724
|
() => reject(new Error(`Agent execution timed out after ${this.config.timeout}ms`)),
|
|
52930
53725
|
this.config.timeout
|
|
52931
53726
|
);
|
|
@@ -52969,6 +53764,8 @@ var init_agent_execution_loop = __esm({
|
|
|
52969
53764
|
error,
|
|
52970
53765
|
durationMs: Date.now() - startTime
|
|
52971
53766
|
};
|
|
53767
|
+
} finally {
|
|
53768
|
+
clearTimeout(timeout);
|
|
52972
53769
|
}
|
|
52973
53770
|
}
|
|
52974
53771
|
/**
|
|
@@ -53288,6 +54085,7 @@ var init_agent_interface = __esm({
|
|
|
53288
54085
|
"libs/sdk/src/common/interfaces/agent.interface.ts"() {
|
|
53289
54086
|
"use strict";
|
|
53290
54087
|
init_agent_execution_loop();
|
|
54088
|
+
init_frontmcp_context_storage();
|
|
53291
54089
|
init_elicitation2();
|
|
53292
54090
|
init_errors();
|
|
53293
54091
|
init_execution_context_interface();
|
|
@@ -53555,6 +54353,10 @@ var init_agent_interface = __esm({
|
|
|
53555
54353
|
async invokeAgent(agentId, input) {
|
|
53556
54354
|
throw new AgentMethodNotAvailableError("invokeAgent", agentId);
|
|
53557
54355
|
}
|
|
54356
|
+
/** An agent calls tools on the `'agent'` surface, as its model's tool calls do. */
|
|
54357
|
+
callToolSurface() {
|
|
54358
|
+
return "agent";
|
|
54359
|
+
}
|
|
53558
54360
|
// ============================================================================
|
|
53559
54361
|
// Notification Methods
|
|
53560
54362
|
// ============================================================================
|
|
@@ -53671,7 +54473,9 @@ var init_agent_interface = __esm({
|
|
|
53671
54473
|
{
|
|
53672
54474
|
sessionId: this.authInfo.sessionId,
|
|
53673
54475
|
getClientCapabilities: (sid) => this.scope.notifications.getClientCapabilities(sid),
|
|
53674
|
-
|
|
54476
|
+
// The request holds what answers this question: a fallback answer the owner already sent
|
|
54477
|
+
// (the re-run of its call), or the 2026-07-28 exchange.
|
|
54478
|
+
tryGetContext: () => this.tryGetContext() ?? this.runningRequestContext(),
|
|
53675
54479
|
entryName: this._agentNameInternal ?? this.agentName,
|
|
53676
54480
|
entryInput: this._agentInputInternal ?? this.input,
|
|
53677
54481
|
elicitationEnabled: this.scope.metadata.elicitation?.enabled === true
|
|
@@ -53681,6 +54485,17 @@ var init_agent_interface = __esm({
|
|
|
53681
54485
|
options
|
|
53682
54486
|
);
|
|
53683
54487
|
}
|
|
54488
|
+
/**
|
|
54489
|
+
* The context of the request this agent runs in. An agent is built with its own provider registry,
|
|
54490
|
+
* which holds no request-scoped instances, so the context is read from the running request.
|
|
54491
|
+
*/
|
|
54492
|
+
runningRequestContext() {
|
|
54493
|
+
try {
|
|
54494
|
+
return this.scope.providers.get(FrontMcpContextStorage).getStore();
|
|
54495
|
+
} catch {
|
|
54496
|
+
return void 0;
|
|
54497
|
+
}
|
|
54498
|
+
}
|
|
53684
54499
|
/**
|
|
53685
54500
|
* Respond with the final output and end execution.
|
|
53686
54501
|
*
|
|
@@ -53836,6 +54651,7 @@ var ChannelContext;
|
|
|
53836
54651
|
var init_channel_interface = __esm({
|
|
53837
54652
|
"libs/sdk/src/common/interfaces/channel.interface.ts"() {
|
|
53838
54653
|
"use strict";
|
|
54654
|
+
init_call_surface();
|
|
53839
54655
|
init_mcp_error();
|
|
53840
54656
|
init_execution_context_interface();
|
|
53841
54657
|
ChannelContext = class extends ExecutionContextBase {
|
|
@@ -53913,13 +54729,27 @@ var init_channel_interface = __esm({
|
|
|
53913
54729
|
this.logger.warn(`Channel "${this.channelName}": pushIncoming called but no handler is wired`);
|
|
53914
54730
|
}
|
|
53915
54731
|
}
|
|
54732
|
+
/**
|
|
54733
|
+
* While it handles an HTTP trigger (a webhook source's request), a channel calls tools on the
|
|
54734
|
+
* `'http-trigger'` surface. Handling any other event, it dispatches in-process.
|
|
54735
|
+
*/
|
|
54736
|
+
callToolSurface() {
|
|
54737
|
+
return getCallSurface() === "http-trigger" ? "http-trigger" : void 0;
|
|
54738
|
+
}
|
|
53916
54739
|
};
|
|
53917
54740
|
}
|
|
53918
54741
|
});
|
|
53919
54742
|
|
|
53920
54743
|
// libs/sdk/src/common/utils/auth-info.utils.ts
|
|
53921
|
-
function
|
|
54744
|
+
function sessionIdPresentedBy(request) {
|
|
54745
|
+
const header = request.headers?.["mcp-session-id"];
|
|
54746
|
+
if (typeof header === "string" && header.length > 0) return header;
|
|
54747
|
+
const query = request.query?.["sessionId"];
|
|
54748
|
+
return typeof query === "string" && query.length > 0 ? query : void 0;
|
|
54749
|
+
}
|
|
54750
|
+
function authInfoFromAuthorization(authorization, presentedSessionId) {
|
|
53922
54751
|
const { token, user, session } = authorization;
|
|
54752
|
+
const verifiedSession = session !== void 0 && presentedSessionId !== void 0 && session.id === presentedSessionId ? session : void 0;
|
|
53923
54753
|
return {
|
|
53924
54754
|
token,
|
|
53925
54755
|
clientId: user?.sub,
|
|
@@ -53929,8 +54759,8 @@ function authInfoFromAuthorization(authorization) {
|
|
|
53929
54759
|
user,
|
|
53930
54760
|
extra: {
|
|
53931
54761
|
user,
|
|
53932
|
-
sessionId:
|
|
53933
|
-
sessionPayload:
|
|
54762
|
+
sessionId: verifiedSession?.id,
|
|
54763
|
+
sessionPayload: verifiedSession?.payload
|
|
53934
54764
|
}
|
|
53935
54765
|
};
|
|
53936
54766
|
}
|
|
@@ -54308,7 +55138,7 @@ var init_decide_request_intent_utils = __esm({
|
|
|
54308
55138
|
});
|
|
54309
55139
|
|
|
54310
55140
|
// libs/sdk/src/common/utils/guard.utils.ts
|
|
54311
|
-
import { isAnonymousSubject as
|
|
55141
|
+
import { isAnonymousSubject as isAnonymousSubject4 } from "@frontmcp/auth";
|
|
54312
55142
|
import {
|
|
54313
55143
|
ConcurrencyLimitError
|
|
54314
55144
|
} from "@frontmcp/guard";
|
|
@@ -54321,15 +55151,19 @@ function enforceIpFilter(scope, clientIp, buildRejection = () => httpRespond.for
|
|
|
54321
55151
|
function buildPartitionContext(source) {
|
|
54322
55152
|
if (!source) return void 0;
|
|
54323
55153
|
const clientId = source.authInfo?.clientId;
|
|
54324
|
-
const userId =
|
|
54325
|
-
const verifiedSessionId = source.authInfo?.sessionId ?? source.authInfo?.extra?.sessionId;
|
|
55154
|
+
const userId = isAnonymousSubject4(clientId) ? void 0 : String(clientId);
|
|
54326
55155
|
const callerKey = userId ? `user:${userId}` : "anonymous";
|
|
54327
55156
|
return {
|
|
54328
|
-
sessionId:
|
|
55157
|
+
sessionId: verifiedSessionOf(source) ?? callerKey,
|
|
54329
55158
|
clientIp: source.metadata?.clientIp,
|
|
54330
55159
|
userId
|
|
54331
55160
|
};
|
|
54332
55161
|
}
|
|
55162
|
+
function verifiedSessionOf(source) {
|
|
55163
|
+
if ("verifiedSessionId" in source) return source.verifiedSessionId;
|
|
55164
|
+
const confirmed = source.authInfo?.sessionId ?? source.authInfo?.extra?.sessionId;
|
|
55165
|
+
return confirmed === source.sessionId ? source.sessionId : void 0;
|
|
55166
|
+
}
|
|
54333
55167
|
function partitionsByIdentity(partitionBy) {
|
|
54334
55168
|
return partitionBy === "session" || partitionBy === "userId" || typeof partitionBy === "function";
|
|
54335
55169
|
}
|
|
@@ -54512,8 +55346,12 @@ var init_primary_auth_provider_interface = __esm({
|
|
|
54512
55346
|
* @param _expectedAudience The protected resource(s) the token must be for
|
|
54513
55347
|
* (`aud` names at least one), when the caller is a
|
|
54514
55348
|
* resource endpoint.
|
|
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.
|
|
54515
55353
|
*/
|
|
54516
|
-
verifyGatewayToken(_token, _requestBaseUrl, _expectedAudience) {
|
|
55354
|
+
verifyGatewayToken(_token, _requestBaseUrl, _expectedAudience, _expectedIssuer) {
|
|
54517
55355
|
return Promise.resolve({
|
|
54518
55356
|
ok: false,
|
|
54519
55357
|
error: "gateway token verification is not supported by this auth mode"
|
|
@@ -54586,7 +55424,10 @@ function findSkillByPath(scope, skillPath) {
|
|
|
54586
55424
|
const segments = skillPath.split("/").filter((s) => s.length > 0);
|
|
54587
55425
|
if (segments.length === 0) return void 0;
|
|
54588
55426
|
const lastSegment = segments[segments.length - 1];
|
|
54589
|
-
|
|
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);
|
|
54590
55431
|
}
|
|
54591
55432
|
async function findAndLoadSkillByPath(scope, skillPath) {
|
|
54592
55433
|
const registry = scope.skills;
|
|
@@ -55045,7 +55886,10 @@ function buildPerSkillResourceRecord(scope, skill, opts = {}) {
|
|
|
55045
55886
|
description: skill.metadata.description,
|
|
55046
55887
|
mimeType: SKILL_MD_MIME_TYPE,
|
|
55047
55888
|
annotations,
|
|
55048
|
-
_meta: meta
|
|
55889
|
+
_meta: meta,
|
|
55890
|
+
// The skill's own `availableWhen`, so `resources/list` and `resources/read` offer its SKILL.md
|
|
55891
|
+
// on the surfaces that offer the skill, and answer others as for an unknown resource.
|
|
55892
|
+
...skill.metadata.availableWhen !== void 0 && { availableWhen: skill.metadata.availableWhen }
|
|
55049
55893
|
};
|
|
55050
55894
|
const handler = async () => {
|
|
55051
55895
|
const { loadResult } = await findAndLoadSkillByPath(scope, skillPath);
|
|
@@ -55119,6 +55963,8 @@ function refreshFromSkill(resource, skill) {
|
|
|
55119
55963
|
}
|
|
55120
55964
|
Object.assign(resource.metadata, extensions);
|
|
55121
55965
|
resource.metadata["description"] = skill.metadata.description;
|
|
55966
|
+
if (skill.metadata.availableWhen === void 0) Reflect.deleteProperty(resource.metadata, "availableWhen");
|
|
55967
|
+
else resource.metadata["availableWhen"] = skill.metadata.availableWhen;
|
|
55122
55968
|
resource.extensionKeys = Object.keys(extensions);
|
|
55123
55969
|
}
|
|
55124
55970
|
var init_sep_2640_per_skill = __esm({
|
|
@@ -55218,11 +56064,23 @@ var init_skill_audit_helper = __esm({
|
|
|
55218
56064
|
});
|
|
55219
56065
|
|
|
55220
56066
|
// libs/sdk/src/skill/skill-scope.helper.ts
|
|
56067
|
+
function declaresDynamicSkills(metadata) {
|
|
56068
|
+
return metadata.dynamicSkills === true || (metadata.plugins ?? []).some((plugin) => declaresDynamicSkills(normalizePlugin(plugin).metadata));
|
|
56069
|
+
}
|
|
56070
|
+
function pluginsRegisterDynamicSkills(plugins) {
|
|
56071
|
+
for (const plugin of plugins) {
|
|
56072
|
+
if (declaresDynamicSkills(normalizePlugin(plugin).metadata)) return true;
|
|
56073
|
+
}
|
|
56074
|
+
return false;
|
|
56075
|
+
}
|
|
55221
56076
|
async function registerSkillCapabilities(options) {
|
|
55222
|
-
const { skillRegistry, flowRegistry, resourceRegistry, providers, skillsConfig, logger } = options;
|
|
56077
|
+
const { skillRegistry, flowRegistry, resourceRegistry, providers, skillsConfig, logger, dynamicSkills } = options;
|
|
55223
56078
|
registerSkillAuditWriter({ providers, audit: skillsConfig?.audit, logger });
|
|
55224
56079
|
await flowRegistry.registryFlows([FilterSkillsFlow]);
|
|
55225
|
-
if (
|
|
56080
|
+
if (dynamicSkills) {
|
|
56081
|
+
skillRegistry.expectDynamicSkills();
|
|
56082
|
+
}
|
|
56083
|
+
if (!skillRegistry.servesSkills()) {
|
|
55226
56084
|
return;
|
|
55227
56085
|
}
|
|
55228
56086
|
await flowRegistry.registryFlows([SearchSkillsFlow, LoadSkillFlow]);
|
|
@@ -55264,6 +56122,7 @@ async function registerSep2640Resources(options) {
|
|
|
55264
56122
|
var init_skill_scope_helper = __esm({
|
|
55265
56123
|
"libs/sdk/src/skill/skill-scope.helper.ts"() {
|
|
55266
56124
|
"use strict";
|
|
56125
|
+
init_plugin_utils();
|
|
55267
56126
|
init_flows2();
|
|
55268
56127
|
init_resources();
|
|
55269
56128
|
init_sep_2640_last_modified();
|
|
@@ -55849,8 +56708,10 @@ var init_session_store_factory = __esm({
|
|
|
55849
56708
|
});
|
|
55850
56709
|
|
|
55851
56710
|
// libs/sdk/src/transport/flows/handle.mcp-20260728.flow.ts
|
|
56711
|
+
import { isAnonymousSubject as isAnonymousSubject5 } from "@frontmcp/auth";
|
|
55852
56712
|
import { z as z102 } from "@frontmcp/lazy-zod";
|
|
55853
56713
|
import { MCP_20260728_META as MCP_20260728_META5 } from "@frontmcp/protocol";
|
|
56714
|
+
import { runRequestExclusive as runRequestExclusive5 } from "@frontmcp/utils";
|
|
55854
56715
|
function frame2(message) {
|
|
55855
56716
|
return encoder3.encode(`event: message
|
|
55856
56717
|
data: ${JSON.stringify(message)}
|
|
@@ -55900,6 +56761,11 @@ async function* streamMessageResponse(options, sink, requestId, runInContext) {
|
|
|
55900
56761
|
}
|
|
55901
56762
|
yield frame2({ jsonrpc: "2.0", id, result: outcome?.result });
|
|
55902
56763
|
}
|
|
56764
|
+
function isAnonymousCaller(auth) {
|
|
56765
|
+
if (!auth) return true;
|
|
56766
|
+
if (!isAnonymousSubject5(auth.user?.sub)) return false;
|
|
56767
|
+
return !auth.token || auth.session?.payload?.isPublic === true;
|
|
56768
|
+
}
|
|
55903
56769
|
function errorResponse(status, error, id) {
|
|
55904
56770
|
return httpRespond.json(
|
|
55905
56771
|
{
|
|
@@ -55942,7 +56808,9 @@ var init_handle_mcp_20260728_flow = __esm({
|
|
|
55942
56808
|
/** Negotiated protocol version for this request. */
|
|
55943
56809
|
version: z102.string().optional(),
|
|
55944
56810
|
requestType: z102.enum(["notification", "subscriptions", "message"]).optional(),
|
|
55945
|
-
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()
|
|
55946
56814
|
});
|
|
55947
56815
|
name38 = "handle:mcp-202607280728";
|
|
55948
56816
|
({ Stage: Stage38 } = FlowHooksOf(name38));
|
|
@@ -55956,11 +56824,7 @@ var init_handle_mcp_20260728_flow = __esm({
|
|
|
55956
56824
|
async parseInput() {
|
|
55957
56825
|
const { request } = this.rawInput;
|
|
55958
56826
|
const auth = request[ServerRequestTokens.auth];
|
|
55959
|
-
this.state.set(
|
|
55960
|
-
stateSchema37.parse({
|
|
55961
|
-
isAnonymous: !auth?.token || auth.token.length === 0 || auth.session?.payload?.isPublic === true
|
|
55962
|
-
})
|
|
55963
|
-
);
|
|
56827
|
+
this.state.set(stateSchema37.parse({ isAnonymous: isAnonymousCaller(auth) }));
|
|
55964
56828
|
}
|
|
55965
56829
|
async validate() {
|
|
55966
56830
|
const { request } = this.rawInput;
|
|
@@ -55993,6 +56857,7 @@ var init_handle_mcp_20260728_flow = __esm({
|
|
|
55993
56857
|
return;
|
|
55994
56858
|
}
|
|
55995
56859
|
this.state.set("version", result.version);
|
|
56860
|
+
this.state.set("clientDeclaredRevision", strictHeaders);
|
|
55996
56861
|
}
|
|
55997
56862
|
/**
|
|
55998
56863
|
* Resolve a tool's input JSON Schema so `x-mcp-header` annotations can be
|
|
@@ -56017,7 +56882,7 @@ var init_handle_mcp_20260728_flow = __esm({
|
|
|
56017
56882
|
if (!context) return (fn) => fn();
|
|
56018
56883
|
const storage = this.scope.providers.get(FrontMcpContextStorage);
|
|
56019
56884
|
return async (fn) => {
|
|
56020
|
-
await storage.runWithContext(context, fn);
|
|
56885
|
+
await runRequestExclusive5(() => storage.runWithContext(context, fn));
|
|
56021
56886
|
};
|
|
56022
56887
|
}
|
|
56023
56888
|
findToolSchema(scope, toolName) {
|
|
@@ -56103,13 +56968,17 @@ var init_handle_mcp_20260728_flow = __esm({
|
|
|
56103
56968
|
clientCapabilities,
|
|
56104
56969
|
frontmcpContext: this.tryGetContext(),
|
|
56105
56970
|
authInfo: auth ? {
|
|
56971
|
+
// No session is presented: this revision has none, so the request has no verified
|
|
56972
|
+
// session (`extra.sessionId`), and anything kept across requests falls back to the
|
|
56973
|
+
// authenticated caller.
|
|
56106
56974
|
...authInfoFromAuthorization(auth),
|
|
56107
|
-
//
|
|
56108
|
-
//
|
|
56109
|
-
//
|
|
56110
|
-
sessionId: auth.session?.id
|
|
56975
|
+
// The shared handlers still key per-request state (provider views, notifications)
|
|
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
|
|
56111
56979
|
} : void 0,
|
|
56112
56980
|
isAnonymous: this.state.required.isAnonymous,
|
|
56981
|
+
clientDeclaredRevision: this.state.clientDeclaredRevision !== false,
|
|
56113
56982
|
composeInstructions: () => this.scope.metadata.instructions,
|
|
56114
56983
|
notificationSink: sink,
|
|
56115
56984
|
traceContext: extractTraceContext(meta)
|
|
@@ -56991,6 +57860,7 @@ var init_handle_streamable_http_flow = __esm({
|
|
|
56991
57860
|
init_ext_apps2();
|
|
56992
57861
|
init_ha_constants();
|
|
56993
57862
|
init_skill_mode_utils();
|
|
57863
|
+
init_mcp_surface();
|
|
56994
57864
|
plan40 = {
|
|
56995
57865
|
pre: ["parseInput", "router"],
|
|
56996
57866
|
execute: ["onInitialize", "onMessage", "onElicitResult", "onSseListener", "onExtApps"],
|
|
@@ -57373,7 +58243,8 @@ var init_handle_streamable_http_flow = __esm({
|
|
|
57373
58243
|
sessionId: session.id,
|
|
57374
58244
|
sessionIdPayload: session.payload,
|
|
57375
58245
|
token
|
|
57376
|
-
}
|
|
58246
|
+
},
|
|
58247
|
+
surface: mcpRequestSurface(this.scope)
|
|
57377
58248
|
}
|
|
57378
58249
|
});
|
|
57379
58250
|
const parsed = CallToolResultSchema4.safeParse(result);
|
|
@@ -59695,6 +60566,41 @@ var init_request_log_redaction = __esm({
|
|
|
59695
60566
|
}
|
|
59696
60567
|
});
|
|
59697
60568
|
|
|
60569
|
+
// libs/sdk/src/transport/persistent-session-owner.ts
|
|
60570
|
+
var persistent_session_owner_exports = {};
|
|
60571
|
+
__export(persistent_session_owner_exports, {
|
|
60572
|
+
claimPersistentSession: () => claimPersistentSession,
|
|
60573
|
+
persistentSessionCallerKey: () => persistentSessionCallerKey
|
|
60574
|
+
});
|
|
60575
|
+
import { isAnonymousSubject as isAnonymousSubject6 } from "@frontmcp/auth";
|
|
60576
|
+
import { sha256Hex as sha256Hex13 } from "@frontmcp/utils";
|
|
60577
|
+
function persistentSessionCallerKey(authorization) {
|
|
60578
|
+
const sub = authorization?.user?.sub;
|
|
60579
|
+
if (!isAnonymousSubject6(sub)) {
|
|
60580
|
+
const iss = authorization?.user?.iss;
|
|
60581
|
+
return `sub:${sha256Hex13(JSON.stringify([typeof iss === "string" ? iss : "", sub]))}`;
|
|
60582
|
+
}
|
|
60583
|
+
if (authorization?.token) return `token:${sha256Hex13(authorization.token)}`;
|
|
60584
|
+
return null;
|
|
60585
|
+
}
|
|
60586
|
+
function claimPersistentSession(session, callerKey, store) {
|
|
60587
|
+
if (!owners.has(session) && store && store.initial !== void 0) {
|
|
60588
|
+
owners.set(session, store.initial);
|
|
60589
|
+
}
|
|
60590
|
+
if (!owners.has(session)) {
|
|
60591
|
+
owners.set(session, callerKey);
|
|
60592
|
+
return "claimed";
|
|
60593
|
+
}
|
|
60594
|
+
return owners.get(session) === callerKey ? "owner" : "refused";
|
|
60595
|
+
}
|
|
60596
|
+
var owners;
|
|
60597
|
+
var init_persistent_session_owner = __esm({
|
|
60598
|
+
"libs/sdk/src/transport/persistent-session-owner.ts"() {
|
|
60599
|
+
"use strict";
|
|
60600
|
+
owners = /* @__PURE__ */ new WeakMap();
|
|
60601
|
+
}
|
|
60602
|
+
});
|
|
60603
|
+
|
|
59698
60604
|
// libs/sdk/src/transport/web-standard-mcp.ts
|
|
59699
60605
|
var web_standard_mcp_exports = {};
|
|
59700
60606
|
__export(web_standard_mcp_exports, {
|
|
@@ -59721,7 +60627,8 @@ async function wireServer(scope, serverOptions, sessionId, sessionIdGenerator, e
|
|
|
59721
60627
|
}
|
|
59722
60628
|
async function buildPersistentWebStandardMcp(scope, options) {
|
|
59723
60629
|
const serverOptions = buildScopedServerOptions(scope);
|
|
59724
|
-
|
|
60630
|
+
const pair = await wireServer(scope, serverOptions, options.sessionId, () => options.sessionId, false);
|
|
60631
|
+
return options.owner ? { ...pair, owner: options.owner } : pair;
|
|
59725
60632
|
}
|
|
59726
60633
|
async function runWebStandardMcp(scope, request, options) {
|
|
59727
60634
|
if (options.persistent) {
|
|
@@ -59730,13 +60637,7 @@ async function runWebStandardMcp(scope, request, options) {
|
|
|
59730
60637
|
const sessionId = `web:${randomUUID26()}`;
|
|
59731
60638
|
const accept = request.headers.get("accept") ?? "";
|
|
59732
60639
|
const wantsStream = request.method === "GET" || options.sse && accept.includes("text/event-stream");
|
|
59733
|
-
const { mcpServer, transport } = await wireServer(
|
|
59734
|
-
scope,
|
|
59735
|
-
options.serverOptions,
|
|
59736
|
-
sessionId,
|
|
59737
|
-
void 0,
|
|
59738
|
-
!wantsStream
|
|
59739
|
-
);
|
|
60640
|
+
const { mcpServer, transport } = await wireServer(scope, options.serverOptions, sessionId, void 0, !wantsStream);
|
|
59740
60641
|
const response = await transport.handleRequest(request, {
|
|
59741
60642
|
authInfo: options.authInfo
|
|
59742
60643
|
});
|
|
@@ -59798,6 +60699,10 @@ var init_http_request_flow = __esm({
|
|
|
59798
60699
|
"router"
|
|
59799
60700
|
],
|
|
59800
60701
|
execute: [
|
|
60702
|
+
// A Durable Object's persistent session serves only the caller that opened
|
|
60703
|
+
// it: anyone else presenting its id is answered as an unknown session. First,
|
|
60704
|
+
// before every protocol handler (2026-07-28 included), so none can serve it.
|
|
60705
|
+
"checkPersistentSessionOwner",
|
|
59801
60706
|
// Protocol 2026-07-28. Runs before EVERY session-era handler — including
|
|
59802
60707
|
// `handleWebFetch`, which answers any request carrying a Web `Request`
|
|
59803
60708
|
// regardless of intent and would otherwise swallow 2026 traffic on a
|
|
@@ -59922,7 +60827,10 @@ var init_http_request_flow = __esm({
|
|
|
59922
60827
|
const { request } = this.rawInput;
|
|
59923
60828
|
this.logger.verbose(`[${this.requestId}] checkAuthorization: verifying session`);
|
|
59924
60829
|
try {
|
|
59925
|
-
const result = await this.scope.runFlow("session:verify", {
|
|
60830
|
+
const result = await this.scope.runFlow("session:verify", {
|
|
60831
|
+
request,
|
|
60832
|
+
sessionless: this.servesProtocol20260728()
|
|
60833
|
+
});
|
|
59926
60834
|
if (!result) {
|
|
59927
60835
|
this.logger.error(`[${this.requestId}] failed to verify session`);
|
|
59928
60836
|
throw new SessionVerificationFailedError();
|
|
@@ -59934,7 +60842,8 @@ var init_http_request_flow = __esm({
|
|
|
59934
60842
|
const ctx = this.tryGetContext();
|
|
59935
60843
|
if (ctx) {
|
|
59936
60844
|
const { token, user } = result.authorization;
|
|
59937
|
-
|
|
60845
|
+
const presentedSessionId = this.servesProtocol20260728() ? void 0 : sessionIdPresentedBy(request);
|
|
60846
|
+
ctx.updateAuthInfo(authInfoFromAuthorization(result.authorization, presentedSessionId));
|
|
59938
60847
|
await this.bindOrchestrationAccessor(ctx, token, user);
|
|
59939
60848
|
}
|
|
59940
60849
|
}
|
|
@@ -59986,6 +60895,24 @@ var init_http_request_flow = __esm({
|
|
|
59986
60895
|
});
|
|
59987
60896
|
ctx.setContextToken(ORCHESTRATED_AUTH_ACCESSOR2, new OrchestratedAuthAccessorAdapter(authorization));
|
|
59988
60897
|
}
|
|
60898
|
+
/**
|
|
60899
|
+
* Whether this request is served under MCP protocol 2026-07-28.
|
|
60900
|
+
*
|
|
60901
|
+
* A V8-isolate deployment (Cloudflare Worker) defaults to the stateless revision: it needs no
|
|
60902
|
+
* session storage, so an unversioned call is served directly instead of minting a session in a
|
|
60903
|
+
* Durable Object. Node keeps defaulting to the session pipeline. Either way an explicit declaration
|
|
60904
|
+
* from the client always wins.
|
|
60905
|
+
*/
|
|
60906
|
+
servesProtocol20260728() {
|
|
60907
|
+
const { request } = this.rawInput;
|
|
60908
|
+
const isWebMode = !!request[ServerRequestTokens.webRequest];
|
|
60909
|
+
const defaultProtocolVersion = this.scope.metadata.transport?.defaultProtocolVersion ?? (isWebMode ? "2026-07-28" : "legacy");
|
|
60910
|
+
return isProtocol20260728Request({
|
|
60911
|
+
headers: request.headers,
|
|
60912
|
+
body: request.body,
|
|
60913
|
+
defaultVersion: defaultProtocolVersion
|
|
60914
|
+
});
|
|
60915
|
+
}
|
|
59989
60916
|
async router() {
|
|
59990
60917
|
try {
|
|
59991
60918
|
const { request } = this.rawInput;
|
|
@@ -60019,12 +60946,7 @@ var init_http_request_flow = __esm({
|
|
|
60019
60946
|
reasons: decision.reasons,
|
|
60020
60947
|
debug: decision.debug
|
|
60021
60948
|
});
|
|
60022
|
-
|
|
60023
|
-
if (isProtocol20260728Request({
|
|
60024
|
-
headers: request.headers,
|
|
60025
|
-
body: request.body,
|
|
60026
|
-
defaultVersion: defaultProtocolVersion
|
|
60027
|
-
})) {
|
|
60949
|
+
if (this.servesProtocol20260728()) {
|
|
60028
60950
|
const verify = this.state.required.verifyResult;
|
|
60029
60951
|
if (verify.kind === "authorized") {
|
|
60030
60952
|
request[ServerRequestTokens.auth] = verify.authorization;
|
|
@@ -60070,6 +60992,11 @@ var init_http_request_flow = __esm({
|
|
|
60070
60992
|
const deleteVerify = this.state.required.verifyResult;
|
|
60071
60993
|
const requestedSessionId = request[ServerRequestTokens.sessionId] ?? request.headers["mcp-session-id"] ?? request.query?.["sessionId"];
|
|
60072
60994
|
const deleteAuthz = authorizeSessionTermination(deleteVerify, requestedSessionId);
|
|
60995
|
+
if (request[ServerRequestTokens.webTransport] && deleteAuthz.kind !== "unauthorized") {
|
|
60996
|
+
request[ServerRequestTokens.auth] = deleteVerify.authorization;
|
|
60997
|
+
this.state.set("intent", decision.intent);
|
|
60998
|
+
return;
|
|
60999
|
+
}
|
|
60073
61000
|
if (deleteAuthz.kind === "unauthorized") {
|
|
60074
61001
|
this.logger.warn(`[${this.requestId}] DELETE session denied: request is not authenticated`);
|
|
60075
61002
|
this.respond(
|
|
@@ -60183,6 +61110,28 @@ var init_http_request_flow = __esm({
|
|
|
60183
61110
|
throw error;
|
|
60184
61111
|
}
|
|
60185
61112
|
}
|
|
61113
|
+
async checkPersistentSessionOwner() {
|
|
61114
|
+
const req = this.rawInput.request;
|
|
61115
|
+
const persistent = req[ServerRequestTokens.webTransport];
|
|
61116
|
+
if (!req[ServerRequestTokens.webRequest] || !persistent || typeof persistent !== "object") return;
|
|
61117
|
+
const { claimPersistentSession: claimPersistentSession2, persistentSessionCallerKey: persistentSessionCallerKey2 } = await Promise.resolve().then(() => (init_persistent_session_owner(), persistent_session_owner_exports));
|
|
61118
|
+
const authorization = req[ServerRequestTokens.auth];
|
|
61119
|
+
const callerKey = persistentSessionCallerKey2(authorization);
|
|
61120
|
+
const store = persistent.owner;
|
|
61121
|
+
const claim = claimPersistentSession2(persistent, callerKey, store);
|
|
61122
|
+
if (claim === "claimed") {
|
|
61123
|
+
await store?.save(callerKey);
|
|
61124
|
+
return;
|
|
61125
|
+
}
|
|
61126
|
+
if (claim === "owner") return;
|
|
61127
|
+
this.logger.warn(`[${this.requestId}] Persistent session refused: it was opened by another caller`);
|
|
61128
|
+
this.respond(
|
|
61129
|
+
httpRespond.json(
|
|
61130
|
+
{ jsonrpc: "2.0", error: { code: -32001, message: "Session not found" }, id: null },
|
|
61131
|
+
{ status: 404 }
|
|
61132
|
+
)
|
|
61133
|
+
);
|
|
61134
|
+
}
|
|
60186
61135
|
async handleWebFetch() {
|
|
60187
61136
|
const { request } = this.rawInput;
|
|
60188
61137
|
const req = request;
|
|
@@ -60197,7 +61146,7 @@ var init_http_request_flow = __esm({
|
|
|
60197
61146
|
const proto = expandProtocolConfig2(scope.metadata.transport?.protocol);
|
|
60198
61147
|
const sse = proto.streamable && !proto.json;
|
|
60199
61148
|
const authorization = req[ServerRequestTokens.auth];
|
|
60200
|
-
const authInfo = authorization ? authInfoFromAuthorization(authorization) : void 0;
|
|
61149
|
+
const authInfo = authorization ? authInfoFromAuthorization(authorization, sessionIdPresentedBy(request)) : void 0;
|
|
60201
61150
|
const persistent = req[ServerRequestTokens.webTransport];
|
|
60202
61151
|
const response = await runWebStandardMcp2(scope, webRequest, {
|
|
60203
61152
|
serverOptions: buildScopedServerOptions2(scope),
|
|
@@ -60403,6 +61352,9 @@ var init_http_request_flow = __esm({
|
|
|
60403
61352
|
__decorateClass([
|
|
60404
61353
|
Stage43("router")
|
|
60405
61354
|
], HttpRequestFlow.prototype, "router", 1);
|
|
61355
|
+
__decorateClass([
|
|
61356
|
+
Stage43("checkPersistentSessionOwner")
|
|
61357
|
+
], HttpRequestFlow.prototype, "checkPersistentSessionOwner", 1);
|
|
60406
61358
|
__decorateClass([
|
|
60407
61359
|
Stage43("handleWebFetch")
|
|
60408
61360
|
], HttpRequestFlow.prototype, "handleWebFetch", 1);
|
|
@@ -60990,7 +61942,11 @@ var init_scope_instance = __esm({
|
|
|
60990
61942
|
if (!this.cliMode) {
|
|
60991
61943
|
await this.buildAuthUiRegistry();
|
|
60992
61944
|
}
|
|
60993
|
-
|
|
61945
|
+
const dynamicSkills = pluginsRegisterDynamicSkills([
|
|
61946
|
+
...serverPlugins,
|
|
61947
|
+
...this.scopeApps.getApps().flatMap((app) => "plugins" in app.metadata ? app.metadata.plugins ?? [] : [])
|
|
61948
|
+
]);
|
|
61949
|
+
if (this.scopeSkills.hasAny() || dynamicSkills) {
|
|
60994
61950
|
const store = createSkillSessionStore({ type: "memory" });
|
|
60995
61951
|
this._skillSession = new SkillSessionManager(
|
|
60996
61952
|
{
|
|
@@ -61041,7 +61997,8 @@ var init_scope_instance = __esm({
|
|
|
61041
61997
|
resourceRegistry: this.scopeResources,
|
|
61042
61998
|
providers: this.scopeProviders,
|
|
61043
61999
|
skillsConfig: this.metadata.skillsConfig,
|
|
61044
|
-
logger: this.logger
|
|
62000
|
+
logger: this.logger,
|
|
62001
|
+
dynamicSkills
|
|
61045
62002
|
});
|
|
61046
62003
|
const jobsConfig = this.metadata.jobs;
|
|
61047
62004
|
const metaRecord = this.metadata;
|
|
@@ -61157,6 +62114,15 @@ var init_scope_instance = __esm({
|
|
|
61157
62114
|
notificationService: this.notificationService,
|
|
61158
62115
|
flowRegistry: this.scopeFlows,
|
|
61159
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
|
+
},
|
|
61160
62126
|
logger: this.logger
|
|
61161
62127
|
});
|
|
61162
62128
|
this._scopeChannels = channelResult.channelRegistry;
|
|
@@ -61561,12 +62527,12 @@ var init_scope_instance = __esm({
|
|
|
61561
62527
|
return this._authoritiesScopeMapping;
|
|
61562
62528
|
}
|
|
61563
62529
|
/**
|
|
61564
|
-
* Collect
|
|
61565
|
-
*
|
|
61566
|
-
*
|
|
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).
|
|
61567
62533
|
*/
|
|
61568
62534
|
getAllSupportedScopes() {
|
|
61569
|
-
const scopes = /* @__PURE__ */ new Set(
|
|
62535
|
+
const scopes = /* @__PURE__ */ new Set();
|
|
61570
62536
|
const collectScopes = (metadata) => {
|
|
61571
62537
|
const authProviders = metadata?.["authProviders"];
|
|
61572
62538
|
if (!Array.isArray(authProviders)) return;
|
|
@@ -61844,7 +62810,7 @@ import {
|
|
|
61844
62810
|
ProviderScope as ProviderScope8,
|
|
61845
62811
|
tokenName as tokenName18
|
|
61846
62812
|
} from "@frontmcp/di";
|
|
61847
|
-
function
|
|
62813
|
+
function verifiedSessionOf2(requestContext) {
|
|
61848
62814
|
const { authInfo } = requestContext;
|
|
61849
62815
|
const verified = authInfo.sessionId ?? authInfo.extra?.["sessionId"];
|
|
61850
62816
|
return typeof verified === "string" && verified.length > 0 && verified !== STATELESS_SESSION_ID ? verified : void 0;
|
|
@@ -62605,7 +63571,7 @@ var init_provider_registry = __esm({
|
|
|
62605
63571
|
if (!(requestContext instanceof FrontMcpContext)) {
|
|
62606
63572
|
return newProviderStore();
|
|
62607
63573
|
}
|
|
62608
|
-
if (!this.sessionCacheEnabled ||
|
|
63574
|
+
if (!this.sessionCacheEnabled || verifiedSessionOf2(requestContext) !== sessionKey) {
|
|
62609
63575
|
let stores = requestContext.get(REQUEST_PROVIDER_STORES);
|
|
62610
63576
|
if (!stores) {
|
|
62611
63577
|
stores = /* @__PURE__ */ new WeakMap();
|
|
@@ -63027,6 +63993,7 @@ var init_web_response_renderer = __esm({
|
|
|
63027
63993
|
});
|
|
63028
63994
|
|
|
63029
63995
|
// libs/sdk/src/transport/web-fetch-handler.ts
|
|
63996
|
+
import { runRequestExclusive as runRequestExclusive6 } from "@frontmcp/utils";
|
|
63030
63997
|
function isCloudflareWorkersRuntime(request) {
|
|
63031
63998
|
if (typeof navigator !== "undefined" && navigator.userAgent === CLOUDFLARE_WORKERS_USER_AGENT) return true;
|
|
63032
63999
|
const cloudflareProperties = request.cf;
|
|
@@ -63140,10 +64107,12 @@ async function runHttpRequestFlowWeb(scope, request, opts = {}) {
|
|
|
63140
64107
|
const serverRequest = await toServerRequest(request, url, opts.ctx, opts.persistent, opts.env);
|
|
63141
64108
|
let output;
|
|
63142
64109
|
try {
|
|
63143
|
-
output = await
|
|
63144
|
-
request
|
|
63145
|
-
|
|
63146
|
-
|
|
64110
|
+
output = await runRequestExclusive6(
|
|
64111
|
+
() => scope.runFlow("http:request", {
|
|
64112
|
+
request: serverRequest,
|
|
64113
|
+
response: {}
|
|
64114
|
+
})
|
|
64115
|
+
);
|
|
63147
64116
|
} catch (error) {
|
|
63148
64117
|
output = flowErrorToHttpOutput(error);
|
|
63149
64118
|
}
|
|
@@ -63156,10 +64125,12 @@ async function runMatchingHttpFlowWeb(scope, request, opts = {}) {
|
|
|
63156
64125
|
if (!flowName) return void 0;
|
|
63157
64126
|
let output;
|
|
63158
64127
|
try {
|
|
63159
|
-
output = await
|
|
63160
|
-
|
|
63161
|
-
|
|
63162
|
-
|
|
64128
|
+
output = await runRequestExclusive6(
|
|
64129
|
+
() => scope.runFlow(flowName, {
|
|
64130
|
+
request: serverRequest,
|
|
64131
|
+
response: {}
|
|
64132
|
+
})
|
|
64133
|
+
);
|
|
63163
64134
|
} catch (error) {
|
|
63164
64135
|
output = flowErrorToHttpOutput(error);
|
|
63165
64136
|
}
|
|
@@ -63170,6 +64141,7 @@ async function toServerRequest(request, url, ctx, persistent, env) {
|
|
|
63170
64141
|
request.headers.forEach((v, k) => {
|
|
63171
64142
|
headers[k] = v;
|
|
63172
64143
|
});
|
|
64144
|
+
headers["host"] = url.host;
|
|
63173
64145
|
const query = {};
|
|
63174
64146
|
url.searchParams.forEach((v, k) => {
|
|
63175
64147
|
const existing = query[k];
|
|
@@ -63200,6 +64172,8 @@ async function toServerRequest(request, url, ctx, persistent, env) {
|
|
|
63200
64172
|
const webRequest = new Request(url.toString(), init);
|
|
63201
64173
|
const serverRequest = {
|
|
63202
64174
|
method,
|
|
64175
|
+
// The scheme the client used, as Express reports it (`req.protocol`).
|
|
64176
|
+
protocol: url.protocol.slice(0, -1),
|
|
63203
64177
|
path: url.pathname,
|
|
63204
64178
|
url: url.pathname + url.search,
|
|
63205
64179
|
headers,
|
|
@@ -63229,10 +64203,57 @@ function findMisconfiguration(error) {
|
|
|
63229
64203
|
if (typeof code === "string" && MISCONFIGURATION_REMEDIES[code]) {
|
|
63230
64204
|
return { code, remedy: MISCONFIGURATION_REMEDIES[code] };
|
|
63231
64205
|
}
|
|
64206
|
+
if (current.name === "ZodError") return { code: "CONFIG_INVALID", remedy: CONFIG_INVALID_REMEDY };
|
|
63232
64207
|
current = current.cause;
|
|
63233
64208
|
}
|
|
63234
64209
|
return void 0;
|
|
63235
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
|
+
}
|
|
63236
64257
|
function flowErrorToHttpOutput(error) {
|
|
63237
64258
|
const misconfiguration = findMisconfiguration(error);
|
|
63238
64259
|
if (misconfiguration) {
|
|
@@ -63266,7 +64287,7 @@ function flowErrorToHttpOutput(error) {
|
|
|
63266
64287
|
}
|
|
63267
64288
|
return { kind: "text", status: 500, body: "Internal Server Error", contentType: "text/plain; charset=utf-8" };
|
|
63268
64289
|
}
|
|
63269
|
-
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;
|
|
63270
64291
|
var init_web_fetch_handler = __esm({
|
|
63271
64292
|
"libs/sdk/src/transport/web-fetch-handler.ts"() {
|
|
63272
64293
|
"use strict";
|
|
@@ -63280,8 +64301,14 @@ var init_web_fetch_handler = __esm({
|
|
|
63280
64301
|
MISCONFIGURATION_REMEDIES = {
|
|
63281
64302
|
SESSION_SECRET_REQUIRED: "Set MCP_SESSION_SECRET in the deployment environment (e.g. `wrangler secret put MCP_SESSION_SECRET`). Session IDs are encrypted with it, and production refuses the development machine-id fallback.",
|
|
63282
64303
|
JWT_SECRET_INVALID: "JWT_SECRET is present but too weak: HS256 requires at least 32 bytes (RFC 7518). Replace it with `openssl rand -hex 32`.",
|
|
63283
|
-
JWT_SECRET_REQUIRED: "Set JWT_SECRET in the deployment environment (e.g. `wrangler secret put JWT_SECRET`). Tokens are signed with it; production refuses the random per-process fallback because tokens would not survive a restart or verify across instances."
|
|
64304
|
+
JWT_SECRET_REQUIRED: "Set JWT_SECRET in the deployment environment (e.g. `wrangler secret put JWT_SECRET`). Tokens are signed with it; production refuses the random per-process fallback because tokens would not survive a restart or verify across instances.",
|
|
64305
|
+
// The startup checks: a server whose entries ask for protection nothing gives them does not start.
|
|
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.",
|
|
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."
|
|
63284
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;
|
|
63285
64312
|
}
|
|
63286
64313
|
});
|
|
63287
64314
|
|
|
@@ -63930,12 +64957,172 @@ var init_front_mcp_providers = __esm({
|
|
|
63930
64957
|
}
|
|
63931
64958
|
});
|
|
63932
64959
|
|
|
64960
|
+
// libs/sdk/src/front-mcp/static-startup.check.ts
|
|
64961
|
+
function tryNormalize(normalize) {
|
|
64962
|
+
try {
|
|
64963
|
+
return normalize();
|
|
64964
|
+
} catch {
|
|
64965
|
+
return void 0;
|
|
64966
|
+
}
|
|
64967
|
+
}
|
|
64968
|
+
function asMetadata(value) {
|
|
64969
|
+
return value !== null && typeof value === "object" ? value : void 0;
|
|
64970
|
+
}
|
|
64971
|
+
function isServedEverywhere(metadata) {
|
|
64972
|
+
const availability = asMetadata(metadata["availableWhen"]);
|
|
64973
|
+
if (!availability) return true;
|
|
64974
|
+
return Object.entries(availability).every(
|
|
64975
|
+
([axis, value]) => value === void 0 || axis === "surface" && Array.isArray(value) && value.length > 0
|
|
64976
|
+
);
|
|
64977
|
+
}
|
|
64978
|
+
function pluginClassOf(record) {
|
|
64979
|
+
switch (record.kind) {
|
|
64980
|
+
case "CLASS_TOKEN" /* CLASS_TOKEN */:
|
|
64981
|
+
return record.provide;
|
|
64982
|
+
case "CLASS" /* CLASS */:
|
|
64983
|
+
return record.useClass;
|
|
64984
|
+
case "VALUE" /* VALUE */:
|
|
64985
|
+
return record.useValue?.constructor;
|
|
64986
|
+
default:
|
|
64987
|
+
return void 0;
|
|
64988
|
+
}
|
|
64989
|
+
}
|
|
64990
|
+
function pluginHooksOf(record) {
|
|
64991
|
+
const pluginClass = pluginClassOf(record);
|
|
64992
|
+
if (!pluginClass) return void 0;
|
|
64993
|
+
return [...collectHook(pluginClass), ...peekPendingTC39HooksForClass(pluginClass)];
|
|
64994
|
+
}
|
|
64995
|
+
function keysEnforcedByPlugins2(plugins, into = /* @__PURE__ */ new Set()) {
|
|
64996
|
+
for (const plugin of plugins ?? []) {
|
|
64997
|
+
const record = tryNormalize(() => normalizePlugin(plugin));
|
|
64998
|
+
if (!record) continue;
|
|
64999
|
+
for (const key of record.metadata.enforcesMetadata ?? []) into.add(key);
|
|
65000
|
+
keysEnforcedByPlugins2(record.metadata.plugins, into);
|
|
65001
|
+
}
|
|
65002
|
+
return into;
|
|
65003
|
+
}
|
|
65004
|
+
function collectStaticEntries(config) {
|
|
65005
|
+
const found = [];
|
|
65006
|
+
const everyAppKeys = /* @__PURE__ */ new Set();
|
|
65007
|
+
const ownAppKeys = /* @__PURE__ */ new Map();
|
|
65008
|
+
const add = (label, metadata, reach) => {
|
|
65009
|
+
const fields = asMetadata(metadata);
|
|
65010
|
+
if (!fields || !isServedEverywhere(fields)) return false;
|
|
65011
|
+
found.push({ label, metadata: fields, reach });
|
|
65012
|
+
return true;
|
|
65013
|
+
};
|
|
65014
|
+
const keysOfApp = (app) => {
|
|
65015
|
+
const keys = ownAppKeys.get(app) ?? /* @__PURE__ */ new Set();
|
|
65016
|
+
ownAppKeys.set(app, keys);
|
|
65017
|
+
return keys;
|
|
65018
|
+
};
|
|
65019
|
+
const visitPlugins = (plugins, app) => {
|
|
65020
|
+
for (const plugin of plugins ?? []) {
|
|
65021
|
+
const record = tryNormalize(() => normalizePlugin(plugin));
|
|
65022
|
+
if (!record) continue;
|
|
65023
|
+
const hooks = pluginHooksOf(record);
|
|
65024
|
+
if (hooks === void 0 || hooks.length > 0) {
|
|
65025
|
+
const gatesOnlyItsApp = hooks !== void 0 && !hooks.some((hook) => hook.appliesTo === "uncovered-apps");
|
|
65026
|
+
const keys = app && gatesOnlyItsApp ? keysOfApp(app) : everyAppKeys;
|
|
65027
|
+
for (const key of record.metadata.enforcesMetadata ?? []) keys.add(key);
|
|
65028
|
+
}
|
|
65029
|
+
visitEntries(record.metadata, app);
|
|
65030
|
+
}
|
|
65031
|
+
};
|
|
65032
|
+
const visitEntries = (owner, app) => {
|
|
65033
|
+
const reach = app ? { app } : "every-plugin";
|
|
65034
|
+
for (const item of owner.tools ?? []) {
|
|
65035
|
+
const record = tryNormalize(() => normalizeTool(item));
|
|
65036
|
+
if (record) add(`Tool "${record.metadata.id || record.metadata.name}"`, record.metadata, reach);
|
|
65037
|
+
}
|
|
65038
|
+
for (const item of owner.resources ?? []) {
|
|
65039
|
+
if (tryNormalize(() => isResourceTemplate(item))) {
|
|
65040
|
+
const record = tryNormalize(() => normalizeResourceTemplate(item));
|
|
65041
|
+
if (record) add(`Resource template "${record.metadata.name}"`, record.metadata, reach);
|
|
65042
|
+
} else {
|
|
65043
|
+
const record = tryNormalize(() => normalizeResource(item));
|
|
65044
|
+
if (record) add(`Resource "${record.metadata.name}"`, record.metadata, reach);
|
|
65045
|
+
}
|
|
65046
|
+
}
|
|
65047
|
+
for (const item of owner.prompts ?? []) {
|
|
65048
|
+
const record = tryNormalize(() => normalizePrompt(item));
|
|
65049
|
+
if (record) add(`Prompt "${record.metadata.name}"`, record.metadata, reach);
|
|
65050
|
+
}
|
|
65051
|
+
for (const item of owner.agents ?? []) {
|
|
65052
|
+
const record = tryNormalize(() => normalizeAgent(item));
|
|
65053
|
+
if (!record) continue;
|
|
65054
|
+
const agentName = record.metadata.id ?? record.metadata.name;
|
|
65055
|
+
if (!add(`Agent "${agentName}"`, record.metadata, reach)) continue;
|
|
65056
|
+
const agentKeys = record.metadata.execution?.useToolFlow === false ? /* @__PURE__ */ new Set() : keysEnforcedByPlugins2(record.metadata.plugins);
|
|
65057
|
+
for (const toolItem of record.metadata.tools ?? []) {
|
|
65058
|
+
const toolRecord = tryNormalize(() => normalizeTool(toolItem));
|
|
65059
|
+
if (toolRecord) add(`Tool "${agentName}:${toolRecord.metadata.name}"`, toolRecord.metadata, { agentKeys });
|
|
65060
|
+
}
|
|
65061
|
+
}
|
|
65062
|
+
for (const item of owner.skills ?? []) {
|
|
65063
|
+
const record = tryNormalize(() => normalizeSkill(item));
|
|
65064
|
+
if (record) add(`Skill "${record.metadata.id ?? record.metadata.name}"`, record.metadata, "every-plugin");
|
|
65065
|
+
}
|
|
65066
|
+
visitPlugins(owner.plugins, app);
|
|
65067
|
+
};
|
|
65068
|
+
const server = config;
|
|
65069
|
+
visitEntries({ skills: server.skills, plugins: server.plugins }, void 0);
|
|
65070
|
+
for (const app of server.apps ?? []) {
|
|
65071
|
+
const record = tryNormalize(() => normalizeApp(app));
|
|
65072
|
+
if (record?.kind === "LOCAL_CLASS" /* LOCAL_CLASS */) visitEntries(record.metadata, record);
|
|
65073
|
+
}
|
|
65074
|
+
const everyPluginKeys = /* @__PURE__ */ new Set([...everyAppKeys, ...[...ownAppKeys.values()].flatMap((keys) => [...keys])]);
|
|
65075
|
+
const keysReaching = (reach) => {
|
|
65076
|
+
if (reach === "every-plugin") return everyPluginKeys;
|
|
65077
|
+
if ("agentKeys" in reach) return reach.agentKeys;
|
|
65078
|
+
return /* @__PURE__ */ new Set([...everyAppKeys, ...ownAppKeys.get(reach.app) ?? []]);
|
|
65079
|
+
};
|
|
65080
|
+
return found.map(({ label, metadata, reach }) => ({ label, metadata, enforcedBy: keysReaching(reach) }));
|
|
65081
|
+
}
|
|
65082
|
+
function assertStaticStartupConfig(config) {
|
|
65083
|
+
const entries = collectStaticEntries(config);
|
|
65084
|
+
if (!config.authorities) {
|
|
65085
|
+
const labels = entries.filter(({ metadata }) => isEnforcementRequested(metadata["authorities"])).map(({ label }) => label);
|
|
65086
|
+
if (labels.length > 0) {
|
|
65087
|
+
const names = labels.slice(0, 5).join(", ");
|
|
65088
|
+
const suffix = labels.length > 5 ? ` and ${labels.length - 5} more` : "";
|
|
65089
|
+
throw new AuthConfigurationError(
|
|
65090
|
+
`Authorities configuration required: ${names}${suffix} declare 'authorities' metadata but authorities enforcement is not fully configured (engine/context builder missing). Add 'authorities: { claimsMapping: {...}, profiles: {...} }' to your @FrontMcp() decorator to enable enforcement, or remove 'authorities' from entry metadata.`,
|
|
65091
|
+
{ suggestion: "Add authorities config to @FrontMcp() or remove authorities from entry metadata" }
|
|
65092
|
+
);
|
|
65093
|
+
}
|
|
65094
|
+
}
|
|
65095
|
+
const problems = entries.flatMap(
|
|
65096
|
+
({ label, metadata, enforcedBy }) => getEnforcedMetadataKeys().filter((key) => isEnforcementRequested(metadata[key]) && !enforcedBy.has(key)).map((key) => {
|
|
65097
|
+
const enforcer = describeMetadataEnforcer(key);
|
|
65098
|
+
return `${label} declares '${key}'${enforcer ? ` (enforced by ${enforcer})` : ""}`;
|
|
65099
|
+
})
|
|
65100
|
+
);
|
|
65101
|
+
if (problems.length > 0) throw new UnenforcedMetadataError(problems);
|
|
65102
|
+
}
|
|
65103
|
+
var init_static_startup_check = __esm({
|
|
65104
|
+
"libs/sdk/src/front-mcp/static-startup.check.ts"() {
|
|
65105
|
+
"use strict";
|
|
65106
|
+
init_agent_utils();
|
|
65107
|
+
init_app_utils();
|
|
65108
|
+
init_hook_decorator();
|
|
65109
|
+
init_enforced_metadata_utils();
|
|
65110
|
+
init_errors();
|
|
65111
|
+
init_hooks_utils();
|
|
65112
|
+
init_plugin_utils();
|
|
65113
|
+
init_prompt_utils();
|
|
65114
|
+
init_resource_utils();
|
|
65115
|
+
init_skill_utils();
|
|
65116
|
+
init_tool_utils();
|
|
65117
|
+
}
|
|
65118
|
+
});
|
|
65119
|
+
|
|
63933
65120
|
// libs/sdk/src/front-mcp/front-mcp.ts
|
|
63934
65121
|
var front_mcp_exports = {};
|
|
63935
65122
|
__export(front_mcp_exports, {
|
|
63936
65123
|
FrontMcpInstance: () => FrontMcpInstance
|
|
63937
65124
|
});
|
|
63938
|
-
import { fileExists as fileExists7, randomUUID as randomUUID28, unlink as unlink2 } from "@frontmcp/utils";
|
|
65125
|
+
import { fileExists as fileExists7, isEdgeRuntime as isEdgeRuntime2, randomUUID as randomUUID28, unlink as unlink2 } from "@frontmcp/utils";
|
|
63939
65126
|
function resolveConfigInput(optionsOrClass) {
|
|
63940
65127
|
if (typeof optionsOrClass === "function") {
|
|
63941
65128
|
const stored = getDecoratorConfig(optionsOrClass);
|
|
@@ -63963,6 +65150,7 @@ var init_front_mcp = __esm({
|
|
|
63963
65150
|
init_task();
|
|
63964
65151
|
init_web_fetch_handler();
|
|
63965
65152
|
init_front_mcp_providers();
|
|
65153
|
+
init_static_startup_check();
|
|
63966
65154
|
stdioServing = false;
|
|
63967
65155
|
FrontMcpInstance = class _FrontMcpInstance2 {
|
|
63968
65156
|
config;
|
|
@@ -64121,17 +65309,30 @@ var init_front_mcp = __esm({
|
|
|
64121
65309
|
* Express / Node `req`/`res` and runs on V8-isolate targets (Cloudflare
|
|
64122
65310
|
* Workers, Deno Deploy, Bun).
|
|
64123
65311
|
*
|
|
65312
|
+
* A server the startup checks refuse (an entry whose `approval`, `featureFlag`
|
|
65313
|
+
* or `authorities` nothing enforces, a missing secret, ...) rejects here, as
|
|
65314
|
+
* with {@link createDirect}. On an edge isolate the server is built on its
|
|
65315
|
+
* first request instead (see below).
|
|
65316
|
+
*
|
|
64124
65317
|
* @example
|
|
64125
65318
|
* // worker entry
|
|
64126
65319
|
* const handler = await FrontMcpInstance.createFetchHandler(config);
|
|
64127
65320
|
* export default { fetch: (request) => handler(request) };
|
|
64128
65321
|
*/
|
|
64129
65322
|
static async createFetchHandler(options) {
|
|
64130
|
-
|
|
64131
|
-
|
|
64132
|
-
|
|
64133
|
-
|
|
64134
|
-
|
|
65323
|
+
const build = async (deferred) => {
|
|
65324
|
+
let frontMcp;
|
|
65325
|
+
try {
|
|
65326
|
+
frontMcp = new _FrontMcpInstance2(frontMcpMetadataSchema.parse(options));
|
|
65327
|
+
await frontMcp.ready;
|
|
65328
|
+
} catch (err) {
|
|
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
|
+
}
|
|
65334
|
+
throw err;
|
|
65335
|
+
}
|
|
64135
65336
|
const scope = frontMcp.getPrimaryScope();
|
|
64136
65337
|
if (!scope) {
|
|
64137
65338
|
throw new ServerNotFoundError();
|
|
@@ -64139,21 +65340,15 @@ var init_front_mcp = __esm({
|
|
|
64139
65340
|
frontMcp.log?.info("FrontMCP fetch handler created (web-standard transport)");
|
|
64140
65341
|
return createWebFetchHandler(scope);
|
|
64141
65342
|
};
|
|
65343
|
+
if (!isEdgeRuntime2()) return build(false);
|
|
65344
|
+
assertStaticStartupConfig(options);
|
|
65345
|
+
const server = createDeferredServerBuild(() => build(true));
|
|
64142
65346
|
return async (request, ctx, env) => {
|
|
64143
|
-
|
|
64144
|
-
|
|
64145
|
-
|
|
64146
|
-
|
|
64147
|
-
|
|
64148
|
-
});
|
|
64149
|
-
}
|
|
64150
|
-
try {
|
|
64151
|
-
inner = await building;
|
|
64152
|
-
} catch (err) {
|
|
64153
|
-
const misconfigured = misconfigurationResponse(err);
|
|
64154
|
-
if (misconfigured) return misconfigured;
|
|
64155
|
-
throw err;
|
|
64156
|
-
}
|
|
65347
|
+
let inner;
|
|
65348
|
+
try {
|
|
65349
|
+
inner = await server.get();
|
|
65350
|
+
} catch (err) {
|
|
65351
|
+
return startupFailureResponse(err, server.retryAfterSeconds());
|
|
64157
65352
|
}
|
|
64158
65353
|
return inner(request, ctx, env);
|
|
64159
65354
|
};
|
|
@@ -64649,7 +65844,8 @@ async function executeTaskWorker(options, taskId) {
|
|
|
64649
65844
|
await runTaskInBackground({
|
|
64650
65845
|
record,
|
|
64651
65846
|
cleanedRequestParams: record.request.params,
|
|
64652
|
-
|
|
65847
|
+
// On the surface of the call that created the task, as the in-process runner runs it.
|
|
65848
|
+
ctx: { authInfo: { sessionId }, ...record.surface !== void 0 && { surface: record.surface } },
|
|
64653
65849
|
scope,
|
|
64654
65850
|
store: taskStore,
|
|
64655
65851
|
registry,
|
|
@@ -66519,6 +67715,7 @@ var init_common = __esm({
|
|
|
66519
67715
|
// libs/sdk/src/index.ts
|
|
66520
67716
|
init_common();
|
|
66521
67717
|
init_front_mcp2();
|
|
67718
|
+
init_static_startup_check();
|
|
66522
67719
|
init_serverless_handler();
|
|
66523
67720
|
init_common();
|
|
66524
67721
|
init_errors();
|
|
@@ -66856,6 +68053,39 @@ init_agent_decorator();
|
|
|
66856
68053
|
|
|
66857
68054
|
// libs/sdk/src/index.ts
|
|
66858
68055
|
init_adapters();
|
|
68056
|
+
|
|
68057
|
+
// libs/sdk/src/hooks/hook-coverage.ts
|
|
68058
|
+
init_lineage_utils();
|
|
68059
|
+
function gateOf(scope, entry) {
|
|
68060
|
+
if ("tool" in entry) {
|
|
68061
|
+
return {
|
|
68062
|
+
flow: "tools:call-tool",
|
|
68063
|
+
ownerId: hookOwnerIdOf(scope.tools.lineageOf(entry.tool) ?? [], entry.tool.owner)
|
|
68064
|
+
};
|
|
68065
|
+
}
|
|
68066
|
+
if ("resource" in entry) {
|
|
68067
|
+
return {
|
|
68068
|
+
flow: "resources:read-resource",
|
|
68069
|
+
ownerId: appOwnerIdOf(scope.resources.lineageOf(entry.resource) ?? [], entry.resource.owner)
|
|
68070
|
+
};
|
|
68071
|
+
}
|
|
68072
|
+
if ("prompt" in entry) {
|
|
68073
|
+
return {
|
|
68074
|
+
flow: "prompts:get-prompt",
|
|
68075
|
+
ownerId: appOwnerIdOf(scope.prompts.lineageOf(entry.prompt) ?? [], entry.prompt.owner)
|
|
68076
|
+
};
|
|
68077
|
+
}
|
|
68078
|
+
return {
|
|
68079
|
+
flow: "skills:filter",
|
|
68080
|
+
ownerId: appOwnerIdOf(scope.skills.lineageOf?.(entry.skill) ?? [], entry.skill.owner)
|
|
68081
|
+
};
|
|
68082
|
+
}
|
|
68083
|
+
function isEntryGatedBy(scope, entry, target) {
|
|
68084
|
+
const { flow, ownerId } = gateOf(scope, entry);
|
|
68085
|
+
return scope.hooks.getFlowHooksForOwner(flow, ownerId).some((hook) => hook.metadata.target === target);
|
|
68086
|
+
}
|
|
68087
|
+
|
|
68088
|
+
// libs/sdk/src/index.ts
|
|
66859
68089
|
init_config();
|
|
66860
68090
|
init_config();
|
|
66861
68091
|
init_direct();
|
|
@@ -66876,6 +68106,7 @@ init_mcp_stateless_client_adapter();
|
|
|
66876
68106
|
init_ext_apps2();
|
|
66877
68107
|
import { PROTOCOL_2026_07_28 as PROTOCOL_2026_07_284 } from "@frontmcp/protocol";
|
|
66878
68108
|
import { InMemoryOrchestratedTokenStore as InMemoryOrchestratedTokenStore2 } from "@frontmcp/auth";
|
|
68109
|
+
import { isAnonymousSubject as isAnonymousSubject7 } from "@frontmcp/auth";
|
|
66879
68110
|
(function suppressZodExpressWarning() {
|
|
66880
68111
|
if (typeof process === "undefined" || typeof process.emitWarning !== "function") return;
|
|
66881
68112
|
const originalEmitWarning = process.emitWarning.bind(process);
|
|
@@ -67270,6 +68501,7 @@ export {
|
|
|
67270
68501
|
SessionClaimConflictError,
|
|
67271
68502
|
SessionHookStage,
|
|
67272
68503
|
SessionIdEmptyError,
|
|
68504
|
+
SessionIdentityRequiredError,
|
|
67273
68505
|
SessionMissingError,
|
|
67274
68506
|
SessionSecretRequiredError,
|
|
67275
68507
|
SessionVerificationFailedError,
|
|
@@ -67397,6 +68629,7 @@ export {
|
|
|
67397
68629
|
appAuthOptionsSchema,
|
|
67398
68630
|
appFilterConfigSchema,
|
|
67399
68631
|
applyPrimitiveFilter,
|
|
68632
|
+
assertStaticStartupConfig,
|
|
67400
68633
|
authInfoFromAuthorization,
|
|
67401
68634
|
authOptionsSchema,
|
|
67402
68635
|
authenticateFnSchema,
|
|
@@ -67442,6 +68675,7 @@ export {
|
|
|
67442
68675
|
create,
|
|
67443
68676
|
createChildSpanContext,
|
|
67444
68677
|
createContextResolver,
|
|
68678
|
+
createDeferredServerBuild,
|
|
67445
68679
|
createDirectResolver,
|
|
67446
68680
|
createElicitationStore,
|
|
67447
68681
|
createElicitationStoreFromStorage,
|
|
@@ -67549,6 +68783,7 @@ export {
|
|
|
67549
68783
|
getElicitationSecret,
|
|
67550
68784
|
getGlobalStoreConfig,
|
|
67551
68785
|
getNestedValue,
|
|
68786
|
+
getPinnedPublicUrl,
|
|
67552
68787
|
getRegistryUrl,
|
|
67553
68788
|
getRequestBaseUrl,
|
|
67554
68789
|
getRunningTool,
|
|
@@ -67568,6 +68803,7 @@ export {
|
|
|
67568
68803
|
incrementalAuthConfigSchema,
|
|
67569
68804
|
intentSchema,
|
|
67570
68805
|
isAlive,
|
|
68806
|
+
isAnonymousSubject7 as isAnonymousSubject,
|
|
67571
68807
|
isAuthError,
|
|
67572
68808
|
isChannelClass,
|
|
67573
68809
|
isChannelFunction,
|
|
@@ -67577,6 +68813,7 @@ export {
|
|
|
67577
68813
|
isDynamicPluginClass,
|
|
67578
68814
|
isElicitationEncryptionAvailable,
|
|
67579
68815
|
isEncryptedBlob,
|
|
68816
|
+
isEntryGatedBy,
|
|
67580
68817
|
isFileInstructions,
|
|
67581
68818
|
isInlineInstructions,
|
|
67582
68819
|
isLazy,
|
|
@@ -67657,6 +68894,7 @@ export {
|
|
|
67657
68894
|
parseTraceContext,
|
|
67658
68895
|
partitionsByIdentity,
|
|
67659
68896
|
pathToEnvKey,
|
|
68897
|
+
peekPendingTC39HooksForClass,
|
|
67660
68898
|
performElicit,
|
|
67661
68899
|
performListRoots,
|
|
67662
68900
|
performSample,
|
|
@@ -67702,6 +68940,7 @@ export {
|
|
|
67702
68940
|
serializeHttpCookie,
|
|
67703
68941
|
serverInfoOptionsSchema,
|
|
67704
68942
|
sessionIdPayloadSchema2 as sessionIdPayloadSchema,
|
|
68943
|
+
sessionIdPresentedBy,
|
|
67705
68944
|
sessionIdSchema,
|
|
67706
68945
|
sessionOptionsSchema,
|
|
67707
68946
|
setNestedValue,
|
|
@@ -67720,6 +68959,7 @@ export {
|
|
|
67720
68959
|
skillsConfigOptionsSchema,
|
|
67721
68960
|
skippedAppBehaviorSchema,
|
|
67722
68961
|
sqliteOptionsSchema,
|
|
68962
|
+
startupFailureResponse,
|
|
67723
68963
|
staticAuthOptionsSchema,
|
|
67724
68964
|
storageProviderSchema,
|
|
67725
68965
|
supportsStreaming,
|