@opengeni/core 2.8.3 → 2.9.1-canary.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/dist/access/external-actor-authority.d.ts +47 -0
  2. package/dist/access/index.d.ts +15 -1
  3. package/dist/application/connect-authority.d.ts +13 -0
  4. package/dist/application/connect-operation.d.ts +28 -0
  5. package/dist/application/external-continuation.d.ts +15 -0
  6. package/dist/application/external-identity-lifecycle.d.ts +20 -0
  7. package/dist/application/external-link-work-admission.d.ts +12 -0
  8. package/dist/application/external-workspace-members.d.ts +6 -0
  9. package/dist/application/host-mcp-owner.d.ts +6 -0
  10. package/dist/application/new-session-drafts.d.ts +2 -1
  11. package/dist/application/session-tenancy.d.ts +1 -0
  12. package/dist/dependencies.d.ts +2 -0
  13. package/dist/domain/capabilities.d.ts +19 -2
  14. package/dist/domain/external-creation-attribution.d.ts +6 -0
  15. package/dist/domain/host-mcp-task-admission.d.ts +18 -0
  16. package/dist/domain/product-integration-pack.d.ts +3 -9
  17. package/dist/domain/product-integration-skill.gen.d.ts +5 -0
  18. package/dist/domain/scheduled-tasks.d.ts +3 -0
  19. package/dist/domain/sessions.d.ts +15 -4
  20. package/dist/index.d.ts +7 -0
  21. package/dist/index.js +1231 -825
  22. package/dist/index.js.map +1 -1
  23. package/dist/remote-mcp-credentials.d.ts +8 -0
  24. package/dist/remote-mcp-credentials.js +219 -0
  25. package/dist/remote-mcp-credentials.js.map +1 -0
  26. package/dist/session-authorization.d.ts +5 -6
  27. package/package.json +17 -13
  28. package/src/access/external-actor-authority.ts +94 -0
  29. package/src/access/index.ts +255 -1
  30. package/src/application/connect-authority.ts +77 -0
  31. package/src/application/connect-operation.ts +51 -0
  32. package/src/application/external-continuation.ts +112 -0
  33. package/src/application/external-identity-lifecycle.ts +48 -0
  34. package/src/application/external-link-work-admission.ts +87 -0
  35. package/src/application/external-workspace-members.ts +95 -0
  36. package/src/application/host-mcp-owner.ts +41 -0
  37. package/src/application/new-session-drafts.ts +9 -2
  38. package/src/application/session-tenancy.ts +24 -3
  39. package/src/application/user-resource-grants.ts +2 -2
  40. package/src/dependencies.ts +2 -0
  41. package/src/domain/capabilities.ts +20 -4
  42. package/src/domain/external-creation-attribution.ts +22 -0
  43. package/src/domain/host-mcp-task-admission.ts +111 -0
  44. package/src/domain/product-integration-pack.ts +11 -464
  45. package/src/domain/product-integration-skill.gen.ts +52 -0
  46. package/src/domain/scheduled-tasks.ts +66 -5
  47. package/src/domain/sessions.ts +253 -23
  48. package/src/index.ts +7 -0
  49. package/src/remote-mcp-credentials.ts +293 -0
  50. package/src/session-authorization.ts +22 -17
@@ -0,0 +1,95 @@
1
+ import type { Context } from "hono";
2
+ import { HTTPException } from "hono/http-exception";
3
+ import {
4
+ AddExternalWorkspaceMemberRequest,
5
+ type ExternalIdentity,
6
+ } from "@opengeni/contracts/external-identities";
7
+ import {
8
+ ensureExternalIdentity,
9
+ grantWorkspaceAccess,
10
+ listWorkspaceMembers,
11
+ lockExternalWorkspaceMembershipLifecycle,
12
+ requireWorkspace,
13
+ setRlsContext,
14
+ withWorkspaceSubjectRls,
15
+ } from "@opengeni/db";
16
+ import {
17
+ accountScopedApiKeyWorkspaceAuthority,
18
+ hasPermission,
19
+ requireAccessContext,
20
+ requireFreshAccessGrant,
21
+ type AccessDeps,
22
+ } from "../access";
23
+
24
+ /** Explicit host onboarding. Ordinary asUser reads never call this operation.
25
+ * Existing memberships are not overwritten, including reduced permissions. */
26
+ export async function addExternalWorkspaceMemberForRequest(
27
+ c: Context,
28
+ deps: AccessDeps,
29
+ workspaceId: string,
30
+ input: unknown,
31
+ ): Promise<ExternalIdentity> {
32
+ const payload = AddExternalWorkspaceMemberRequest.parse(input);
33
+ const context = await requireAccessContext(c, deps);
34
+ const authority = accountScopedApiKeyWorkspaceAuthority(context);
35
+ if (!authority)
36
+ throw new HTTPException(403, {
37
+ message: "external onboarding requires an organization service key",
38
+ });
39
+ const grant = await requireFreshAccessGrant(c, deps, workspaceId, "members:manage");
40
+ if (
41
+ grant.accountId !== authority.accountId ||
42
+ payload.permissions.some((permission) => !hasPermission(grant.permissions, permission))
43
+ ) {
44
+ throw new HTTPException(403, { message: "membership exceeds key authority" });
45
+ }
46
+ return withWorkspaceSubjectRls(deps.db, workspaceId, grant.subjectId, async (tx) => {
47
+ await lockExternalWorkspaceMembershipLifecycle(tx, grant.accountId);
48
+ const live = await requireFreshAccessGrant(
49
+ c,
50
+ { ...deps, db: tx },
51
+ workspaceId,
52
+ "members:manage",
53
+ );
54
+ if (
55
+ live.accountId !== grant.accountId ||
56
+ live.subjectId !== grant.subjectId ||
57
+ payload.permissions.some((permission) => !hasPermission(live.permissions, permission))
58
+ ) {
59
+ throw new HTTPException(403, { message: "membership authority changed" });
60
+ }
61
+ const workspace = await requireWorkspace(tx, workspaceId);
62
+ if (workspace.kind !== "shared" || workspace.accountId !== authority.accountId)
63
+ throw new HTTPException(403, {
64
+ message: "external onboarding requires a shared organization workspace",
65
+ });
66
+ const identity = await ensureExternalIdentity(tx, {
67
+ accountId: authority.accountId,
68
+ ...payload.identity,
69
+ });
70
+ await setRlsContext(tx, { accountId: authority.accountId, workspaceId });
71
+ const existing = (await listWorkspaceMembers(tx, workspaceId)).find(
72
+ (member) => member.subjectId === identity.subjectId,
73
+ );
74
+ const permissions = [...new Set(payload.permissions)];
75
+ if (existing) {
76
+ if (
77
+ existing.permissions.length !== permissions.length ||
78
+ existing.permissions.some((permission) => !permissions.includes(permission))
79
+ ) {
80
+ throw new HTTPException(409, {
81
+ message: "existing membership differs; onboarding does not overwrite permissions",
82
+ });
83
+ }
84
+ return identity;
85
+ }
86
+ await grantWorkspaceAccess(tx, {
87
+ accountId: authority.accountId,
88
+ workspaceId,
89
+ subjectId: identity.subjectId,
90
+ role: "member",
91
+ permissions,
92
+ });
93
+ return identity;
94
+ });
95
+ }
@@ -0,0 +1,41 @@
1
+ import { HTTPException } from "hono/http-exception";
2
+ import { resolveHostMcpBindingOwner, type Database } from "@opengeni/db";
3
+ import {
4
+ externalActorContinuationForAuthorization,
5
+ hasPermission,
6
+ hasVerifiedOwningUserAuthorization,
7
+ requireResolvedAccessGrantAuthorization,
8
+ type AccessGrantAuthorization,
9
+ } from "../access";
10
+ import { requireConnectOwnerAuthority } from "./connect-authority";
11
+
12
+ /** Direct user admission only. Agents inherit exact accepted snapshots instead.
13
+ * Native and externally represented users share owner semantics; service keys
14
+ * cannot impersonate a human owner merely by naming a subject. */
15
+ export function prepareHostMcpOwnerAuthorization(
16
+ authorization: AccessGrantAuthorization,
17
+ workspaceId: string,
18
+ permission: "connections:read" | "connections:write",
19
+ ) {
20
+ const grant = requireResolvedAccessGrantAuthorization(authorization, workspaceId);
21
+ if (
22
+ !hasVerifiedOwningUserAuthorization(authorization) ||
23
+ grant.metadata?.sessionId ||
24
+ !hasPermission(grant.permissions, permission)
25
+ )
26
+ throw new HTTPException(403, {
27
+ message: "Host binding requires verified owning-user authority",
28
+ });
29
+ const continuation = externalActorContinuationForAuthorization(authorization);
30
+ const scope = {
31
+ accountId: grant.accountId,
32
+ workspaceId,
33
+ subjectId: grant.subjectId,
34
+ personalOwnerVerified: authorization.canonicalManagedHumanSession,
35
+ ...(continuation ? { externalContinuation: continuation } : {}),
36
+ };
37
+ return async (tx: Database) => {
38
+ await requireConnectOwnerAuthority(tx, scope, permission);
39
+ return resolveHostMcpBindingOwner(tx, scope);
40
+ };
41
+ }
@@ -29,7 +29,11 @@ import {
29
29
  validateGitHubRepositorySelection,
30
30
  validateToolRefs,
31
31
  } from "../domain/resources";
32
- import { hasPermission } from "../access";
32
+ import {
33
+ hasPermission,
34
+ externalAttributionForAuthorization,
35
+ type AccessGrantAuthorization,
36
+ } from "../access";
33
37
  import { assertConfiguredModel, assertWorkspaceModelPolicyAllows } from "../domain/sessions";
34
38
 
35
39
  type NewSessionDraftDependencies = Pick<AppDependencies, "settings" | "db" | "objectStorage">;
@@ -227,6 +231,7 @@ export async function saveActorNewSessionDraft(
227
231
  * the historical bare-membership fence.
228
232
  */
229
233
  canonicalManagedHumanSession = false,
234
+ externalAuthorization?: AccessGrantAuthorization,
230
235
  ): Promise<NewSessionDraftValue> {
231
236
  const input = SaveNewSessionDraftRequest.parse(rawInput);
232
237
  // The pre-marker client contract required `tools` and had no
@@ -280,7 +285,9 @@ export async function saveActorNewSessionDraft(
280
285
  // all, so the human-removal fence above must fall back to the
281
286
  // organization-membership pointer for them — and only for the
282
287
  // canonical managed-cookie session that owns it.
283
- personalWorkspaceOwnerException: canonicalManagedHumanSession,
288
+ personalWorkspaceOwnerException:
289
+ canonicalManagedHumanSession ||
290
+ externalAttributionForAuthorization(externalAuthorization, grant) !== null,
284
291
  }),
285
292
  ),
286
293
  );
@@ -26,10 +26,15 @@ import {
26
26
  type ForkSessionContentResult,
27
27
  } from "@opengeni/db";
28
28
  import { publishDurableSessionEvents, type EventBus } from "@opengeni/events";
29
- import { requirePermission, type AccessGrantAuthorization } from "../access";
29
+ import {
30
+ requirePermission,
31
+ hasVerifiedOwningUserAuthorization,
32
+ type AccessGrantAuthorization,
33
+ } from "../access";
30
34
  import { requireSessionAuthorization } from "../session-authorization";
31
35
  import type { AppDependencies } from "../dependencies";
32
36
  import { validateVariableSetAttachment } from "../domain/environments";
37
+ import { externalContinuationCommitAuthorizer } from "./external-continuation";
33
38
 
34
39
  type SessionTenancyDependencies = Pick<
35
40
  AppDependencies,
@@ -64,6 +69,18 @@ export function requireCanonicalManagedHuman(
64
69
  }
65
70
  }
66
71
 
72
+ export function requireVerifiedOwningUser(
73
+ authorization: AccessGrantAuthorization,
74
+ workspaceId: string,
75
+ ): void {
76
+ if (
77
+ !hasVerifiedOwningUserAuthorization(authorization) ||
78
+ authorization.grant.workspaceId !== workspaceId
79
+ ) {
80
+ throw new SessionTenancyManagedHumanRequiredError();
81
+ }
82
+ }
83
+
67
84
  export async function getManagedHumanSessionCreateCapabilities(
68
85
  deps: Pick<SessionTenancyDependencies, "db">,
69
86
  authorization: AccessGrantAuthorization,
@@ -71,7 +88,7 @@ export async function getManagedHumanSessionCreateCapabilities(
71
88
  ): Promise<SessionTenancyCreateCapabilities> {
72
89
  requirePermission(authorization.grant, "sessions:create");
73
90
  try {
74
- requireCanonicalManagedHuman(authorization, workspaceId);
91
+ requireVerifiedOwningUser(authorization, workspaceId);
75
92
  } catch (error) {
76
93
  if (error instanceof SessionTenancyManagedHumanRequiredError) {
77
94
  return SessionTenancyCreateCapabilities.parse({
@@ -132,7 +149,7 @@ async function requireSessionTenancyMutationGate(
132
149
  // These checks are deliberately target-free. A rejected principal must not
133
150
  // cause a session lookup or embedding-host callback that distinguishes a
134
151
  // missing, shared, or another owner's private session.
135
- requireCanonicalManagedHuman(authorization, workspaceId);
152
+ requireVerifiedOwningUser(authorization, workspaceId);
136
153
  for (const permission of permissions) requirePermission(authorization.grant, permission);
137
154
  if (!(await sessionTenancyProductActivated(deps.db, workspaceId))) {
138
155
  throw new SessionTenancyNotActivatedError();
@@ -234,6 +251,7 @@ export async function updateManagedHumanSessionVisibility(
234
251
  request: UpdateSessionVisibilityRequest,
235
252
  authorizationSurface: SessionAuthorizationSurface = "core",
236
253
  ): Promise<UpdateSessionVisibilityResponse> {
254
+ const beforeCommit = externalContinuationCommitAuthorizer(authorization);
237
255
  await requireSessionTenancyMutationGate(deps, authorization, workspaceId, ["sessions:control"]);
238
256
  await requireSessionAuthorization(deps, authorization.grant, {
239
257
  sessionId,
@@ -250,6 +268,7 @@ export async function updateManagedHumanSessionVisibility(
250
268
  targetVisibility: sessionVisibilityFromPublic(request.visibility),
251
269
  expectedAuthorityEpoch: request.expectedAuthorityEpoch,
252
270
  operationKey: request.idempotencyKey,
271
+ ...(beforeCommit ? { beforeCommit } : {}),
253
272
  }),
254
273
  );
255
274
  const response = UpdateSessionVisibilityResponse.parse({
@@ -284,6 +303,7 @@ export async function forkManagedHumanSession(
284
303
  "sessions:read",
285
304
  "sessions:create",
286
305
  ]);
306
+ const beforeCommit = externalContinuationCommitAuthorizer(authorization);
287
307
  const forkInput = {
288
308
  sourceWorkspaceId: workspaceId,
289
309
  sourceSessionId,
@@ -294,6 +314,7 @@ export async function forkManagedHumanSession(
294
314
  request.visibility === "private" ? ("user_private" as const) : ("workspace_shared" as const),
295
315
  workspaceSharedAcknowledged: request.workspaceSharedAcknowledged,
296
316
  operationKey: request.idempotencyKey,
317
+ ...(beforeCommit ? { beforeCommit } : {}),
297
318
  ...(request.rigId !== undefined || request.variableSetIds !== undefined
298
319
  ? {
299
320
  runtimeRequest: {
@@ -16,7 +16,7 @@ import { requirePermission, type AccessGrantAuthorization } from "../access";
16
16
  import type { AppDependencies } from "../dependencies";
17
17
  import { requireSessionAuthorization } from "../session-authorization";
18
18
  import {
19
- requireCanonicalManagedHuman,
19
+ requireVerifiedOwningUser,
20
20
  SessionTenancyManagedHumanRequiredError,
21
21
  } from "./session-tenancy";
22
22
 
@@ -58,7 +58,7 @@ function requireOwnerAuthority(
58
58
  permissions: readonly Permission[],
59
59
  ): void {
60
60
  if (!authorization.canonicalLocalHumanSession) {
61
- requireCanonicalManagedHuman(authorization, workspaceId);
61
+ requireVerifiedOwningUser(authorization, workspaceId);
62
62
  } else if (
63
63
  !authorization.contextIntegrity ||
64
64
  authorization.authenticatedSubjectId !== authorization.grant.subjectId ||
@@ -208,6 +208,8 @@ export type AppDependencies = {
208
208
  fikenFetch?: typeof fetch;
209
209
  /** Injectable Integration Definition OAuth/API transport for deterministic tests. */
210
210
  apiIntegrationOAuthFetch?: typeof fetch;
211
+ /** Injectable specification/introspection transport, still network-policy checked. */
212
+ apiIntegrationSourceFetch?: typeof fetch;
211
213
  atlassianFetch?: typeof fetch;
212
214
  /** Injectable MCP OAuth setup deadline for deterministic stalled-provider tests. */
213
215
  oauthStartDeadlineMs?: number;
@@ -231,7 +231,7 @@ export async function createCatalogItem(input: {
231
231
  });
232
232
  }
233
233
 
234
- export async function enableCapability(input: {
234
+ type EnableCapabilityInput = {
235
235
  db: Database;
236
236
  grant: AccessGrant;
237
237
  accountId: string;
@@ -240,13 +240,28 @@ export async function enableCapability(input: {
240
240
  capabilityId: string;
241
241
  payload: EnableCapabilityRequest;
242
242
  probeMcpServer?: McpCapabilityProbe;
243
- }): Promise<CapabilityInstallation> {
243
+ };
244
+
245
+ export async function enableCapability(
246
+ input: EnableCapabilityInput,
247
+ ): Promise<CapabilityInstallation> {
248
+ const prepared = await prepareCapabilityEnable(input);
249
+ return prepared.commit(input.db);
250
+ }
251
+
252
+ /** Probe outside a durable Connect commit; persist the exact prepared settings
253
+ * inside the caller's authorized receipt transaction. Native enable uses this too. */
254
+ export async function prepareCapabilityEnable(input: EnableCapabilityInput) {
244
255
  const item = await requireCatalogItem(
245
256
  input.db,
246
257
  input.workspaceId,
247
258
  input.settings,
248
259
  input.capabilityId,
249
260
  );
261
+ if (isReservedCodexAppsCatalogItem(item))
262
+ throw new HTTPException(422, {
263
+ message: "Codex Apps use the dedicated account designation flow",
264
+ });
250
265
  if (item.kind === "skill") {
251
266
  throw new HTTPException(409, {
252
267
  message: "Install Skills through the Skill library or source import flow",
@@ -303,14 +318,15 @@ export async function enableCapability(input: {
303
318
  );
304
319
  }
305
320
  }
306
- return await enableCapabilityInstallation(input.db, {
321
+ const installation = {
307
322
  accountId: input.accountId,
308
323
  workspaceId: input.workspaceId,
309
324
  capabilityId: item.id,
310
325
  kind: item.kind,
311
326
  config: installationConfig,
312
327
  metadata: installationMetadata,
313
- });
328
+ };
329
+ return { commit: (db: Database) => enableCapabilityInstallation(db, installation) };
314
330
  }
315
331
 
316
332
  /**
@@ -0,0 +1,22 @@
1
+ import type { AccessGrant } from "@opengeni/contracts";
2
+ import { HTTPException } from "hono/http-exception";
3
+ import { externalAttributionForAuthorization, type AccessGrantAuthorization } from "../access";
4
+
5
+ export const EXTERNAL_CREATION_ATTRIBUTION_KEY = "opengeniExternalCreationAttribution";
6
+
7
+ /** Retained alongside session.created metadata; this is not a live permission
8
+ * snapshot and must never authorize a follow-up, child, or scheduled turn. */
9
+ export function externalCreationMetadata(
10
+ metadata: Record<string, unknown> | undefined,
11
+ authorization: AccessGrantAuthorization | undefined,
12
+ grant: AccessGrant,
13
+ ): Record<string, unknown> | undefined {
14
+ if (metadata && Object.hasOwn(metadata, EXTERNAL_CREATION_ATTRIBUTION_KEY)) {
15
+ throw new HTTPException(422, {
16
+ message: `${EXTERNAL_CREATION_ATTRIBUTION_KEY} is server-owned`,
17
+ });
18
+ }
19
+ const attribution = externalAttributionForAuthorization(authorization, grant);
20
+ if (!attribution) return metadata;
21
+ return { ...metadata, [EXTERNAL_CREATION_ATTRIBUTION_KEY]: attribution };
22
+ }
@@ -0,0 +1,111 @@
1
+ import { HTTPException } from "hono/http-exception";
2
+ import type { Settings } from "@opengeni/config";
3
+ import type { ToolRef, ScheduledTask, AccessGrant } from "@opengeni/contracts";
4
+ import {
5
+ HostMcpBindingDefinition,
6
+ HostMcpCreateSelections,
7
+ } from "@opengeni/contracts/host-mcp-bindings";
8
+ import {
9
+ captureHostMcpTaskAuthorities,
10
+ HostMcpDelegationAuthorityError,
11
+ inheritHostMcpTaskAuthoritiesFromAttempt,
12
+ type Database,
13
+ } from "@opengeni/db";
14
+ import { hasPermission, type AccessGrantAuthorization } from "../access";
15
+ import { prepareHostMcpOwnerAuthorization } from "../application/host-mcp-owner";
16
+ import { assertHostMcpAuthoritySourceAdmissionEnabled } from "./host-mcp-authority-source-admission";
17
+
18
+ /** Internal closure, not a caller-provided callback or durable key dependency. */
19
+ export function prepareHostMcpTaskAdmission(input: {
20
+ settings: Settings;
21
+ tools: ToolRef[];
22
+ grant: AccessGrant;
23
+ authorization?: AccessGrantAuthorization;
24
+ selections: unknown;
25
+ }): (tx: Database, task: ScheduledTask) => Promise<void> {
26
+ const selections = HostMcpCreateSelections.parse(input.selections);
27
+ if (
28
+ !input.authorization ||
29
+ input.authorization.grant.accountId !== input.grant.accountId ||
30
+ input.authorization.grant.subjectId !== input.grant.subjectId ||
31
+ input.authorization?.grant.workspaceId !== input.grant.workspaceId ||
32
+ input.grant.metadata?.["sessionId"] ||
33
+ !hasPermission(input.authorization?.grant.permissions ?? [], "connections:read") ||
34
+ !hasPermission(input.grant.permissions, "connections:read")
35
+ )
36
+ throw new HTTPException(403, {
37
+ message: "Host task selection requires a verified owner",
38
+ });
39
+ const reauthorize = prepareHostMcpOwnerAuthorization(
40
+ input.authorization,
41
+ input.grant.workspaceId,
42
+ "connections:read",
43
+ );
44
+ const prepared = selections.map((selection) => {
45
+ const server = input.settings.mcpServers.find(
46
+ (candidate) => candidate.id === selection.serverId,
47
+ );
48
+ if (
49
+ !server?.url ||
50
+ server.connectionRef?.authoritySource !== "host" ||
51
+ !server.connectionRef.hostBinding ||
52
+ !input.tools.some((tool) => tool.kind === "mcp" && tool.id === selection.serverId)
53
+ )
54
+ throw new HTTPException(422, {
55
+ message: "Host task selection must match a selected configured server",
56
+ });
57
+ assertHostMcpAuthoritySourceAdmissionEnabled(input.settings, server.connectionRef);
58
+ const { hostBinding, ...connectionRef } = server.connectionRef;
59
+ return {
60
+ delegationId: selection.delegationId,
61
+ generation: selection.generation,
62
+ bindingId: hostBinding.bindingId,
63
+ bindingGeneration: hostBinding.generation,
64
+ definition: HostMcpBindingDefinition.parse({
65
+ serverId: selection.serverId,
66
+ destinationUrl: server.url,
67
+ connectionRef,
68
+ }),
69
+ };
70
+ });
71
+ return async (tx, task) => {
72
+ const owner = await reauthorize(tx);
73
+ try {
74
+ if (prepared.length) await captureHostMcpTaskAuthorities(tx, owner, task, prepared);
75
+ } catch (error) {
76
+ if (error instanceof HostMcpDelegationAuthorityError)
77
+ throw new HTTPException(403, { message: error.message });
78
+ throw error;
79
+ }
80
+ };
81
+ }
82
+
83
+ export function prepareInheritedHostMcpTaskAdmission(
84
+ settings: Settings,
85
+ tools: ToolRef[],
86
+ source: { sessionId: string; turnId: string; attemptId: string; executionGeneration: number },
87
+ ): (tx: Database, task: ScheduledTask) => Promise<void> {
88
+ const configured = settings.mcpServers.flatMap((server) => {
89
+ if (
90
+ !tools.some((tool) => tool.kind === "mcp" && tool.id === server.id) ||
91
+ server.connectionRef?.authoritySource !== "host" ||
92
+ !server.connectionRef.hostBinding ||
93
+ !server.url
94
+ )
95
+ return [];
96
+ assertHostMcpAuthoritySourceAdmissionEnabled(settings, server.connectionRef);
97
+ const { hostBinding, ...connectionRef } = server.connectionRef;
98
+ return [
99
+ {
100
+ bindingId: hostBinding.bindingId,
101
+ bindingGeneration: hostBinding.generation,
102
+ definition: HostMcpBindingDefinition.parse({
103
+ serverId: server.id,
104
+ destinationUrl: server.url,
105
+ connectionRef,
106
+ }),
107
+ },
108
+ ];
109
+ });
110
+ return (tx, task) => inheritHostMcpTaskAuthoritiesFromAttempt(tx, task, source, configured);
111
+ }