@opengeni/core 0.28.1 → 1.0.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 (224) hide show
  1. package/README.md +1 -1
  2. package/dist/access/external-actor-authority.d.ts +47 -0
  3. package/dist/access/index.d.ts +182 -3
  4. package/dist/access/service-initiator.d.ts +3 -0
  5. package/dist/application/api-integration-servers.d.ts +20 -0
  6. package/dist/application/archived-session-imports.d.ts +6 -0
  7. package/dist/application/composer-submit.d.ts +24 -0
  8. package/dist/application/connect-authority.d.ts +13 -0
  9. package/dist/application/connect-operation.d.ts +31 -0
  10. package/dist/application/external-continuation.d.ts +15 -0
  11. package/dist/application/external-identity-lifecycle.d.ts +20 -0
  12. package/dist/application/external-link-work-admission.d.ts +12 -0
  13. package/dist/application/external-workspace-members.d.ts +34 -0
  14. package/dist/application/modal-native-worker-host-transport.d.ts +297 -0
  15. package/dist/application/new-session-drafts.d.ts +39 -2
  16. package/dist/application/sandbox-recovery.d.ts +58 -0
  17. package/dist/application/session-commands.d.ts +26 -13
  18. package/dist/application/session-mcp-credential-rotation.d.ts +29 -0
  19. package/dist/application/session-tenancy.d.ts +19 -0
  20. package/dist/application/user-resource-grants.d.ts +20 -0
  21. package/dist/billing/limits.d.ts +11 -0
  22. package/dist/canonical-human-identities.d.ts +7 -2
  23. package/dist/canonical-human-identities.js +17 -4
  24. package/dist/canonical-human-identities.js.map +1 -1
  25. package/dist/{chunk-6WKPWE5S.js → chunk-2MKMRGSY.js} +108 -24
  26. package/dist/chunk-2MKMRGSY.js.map +1 -0
  27. package/dist/{chunk-NYPMQ7NO.js → chunk-GVO3N37G.js} +205 -37
  28. package/dist/chunk-GVO3N37G.js.map +1 -0
  29. package/dist/{chunk-JJU6I5XD.js → chunk-LCQXNR62.js} +36 -10
  30. package/dist/chunk-LCQXNR62.js.map +1 -0
  31. package/dist/chunk-MM6N7CTO.js +387 -0
  32. package/dist/chunk-MM6N7CTO.js.map +1 -0
  33. package/dist/chunk-QO5GVFFO.js +235 -0
  34. package/dist/chunk-QO5GVFFO.js.map +1 -0
  35. package/dist/codex-model-availability.d.ts +14 -0
  36. package/dist/default-session-model.d.ts +139 -0
  37. package/dist/dependencies.d.ts +87 -4
  38. package/dist/developer-setup-credential-ceiling.d.ts +10 -0
  39. package/dist/domain/agent-config-resolution.d.ts +49 -0
  40. package/dist/domain/automations.d.ts +58 -0
  41. package/dist/domain/capabilities.d.ts +22 -4
  42. package/dist/domain/company-profile-agent-admin.d.ts +25 -0
  43. package/dist/domain/connection-account-selection-error.d.ts +8 -0
  44. package/dist/domain/connector-tool-permissions.d.ts +36 -0
  45. package/dist/domain/direct-model-provider.d.ts +3 -0
  46. package/dist/domain/editable-artifacts/agent-application.d.ts +10 -1
  47. package/dist/domain/editable-artifacts/durable-export.d.ts +22 -1
  48. package/dist/domain/editable-artifacts/ports.d.ts +10 -0
  49. package/dist/domain/editable-artifacts/service.d.ts +4 -3
  50. package/dist/domain/environments.d.ts +5 -9
  51. package/dist/domain/external-creation-attribution.d.ts +6 -0
  52. package/dist/domain/file-owner.d.ts +8 -0
  53. package/dist/domain/github-action-policies.d.ts +36 -0
  54. package/dist/domain/github-repository-bindings.d.ts +117 -0
  55. package/dist/domain/github-skill-source.d.ts +5 -0
  56. package/dist/domain/governed-learning-slack-publication.d.ts +45 -0
  57. package/dist/domain/insights-timing.d.ts +16 -0
  58. package/dist/domain/insights-usage.d.ts +27 -0
  59. package/dist/domain/insights.d.ts +28 -1
  60. package/dist/domain/knowledge-files.d.ts +20 -0
  61. package/dist/domain/knowledge-messages.d.ts +39 -0
  62. package/dist/domain/knowledge-messages.test.d.ts +1 -0
  63. package/dist/domain/knowledge-preparation.d.ts +10 -0
  64. package/dist/domain/knowledge-preparation.test.d.ts +1 -0
  65. package/dist/domain/knowledge-search.d.ts +15 -0
  66. package/dist/domain/knowledge-search.test.d.ts +1 -0
  67. package/dist/domain/knowledge.d.ts +9 -0
  68. package/dist/domain/mcp-account-bindings.d.ts +19 -0
  69. package/dist/domain/mcp-account-routes.d.ts +21 -0
  70. package/dist/domain/native-mcp-connection-admission.d.ts +3 -0
  71. package/dist/domain/organization-integration-catalog.d.ts +15 -0
  72. package/dist/domain/organization-membership-lifecycle.d.ts +1 -0
  73. package/dist/domain/personal-connection-delegations.d.ts +87 -4
  74. package/dist/domain/pr-review.d.ts +249 -0
  75. package/dist/domain/resources.d.ts +24 -3
  76. package/dist/domain/scheduled-task-access.d.ts +247 -0
  77. package/dist/domain/scheduled-tasks.d.ts +103 -6
  78. package/dist/domain/session-connection-accounts.d.ts +16 -0
  79. package/dist/domain/session-file-access.d.ts +4 -0
  80. package/dist/domain/session-native-connection-replacement.d.ts +14 -0
  81. package/dist/domain/session-start-timing.d.ts +7 -0
  82. package/dist/domain/session-tool-policy.d.ts +93 -4
  83. package/dist/domain/sessions.d.ts +275 -28
  84. package/dist/domain/skill-imports.d.ts +1 -0
  85. package/dist/domain/skill-search.d.ts +30 -0
  86. package/dist/domain/skills.d.ts +117 -0
  87. package/dist/domain/slack-bot.d.ts +46 -1
  88. package/dist/domain/workspace-members.d.ts +8 -1
  89. package/dist/editable-artifact-live/application.d.ts +12 -3
  90. package/dist/editable-artifact-live/ports.d.ts +0 -1
  91. package/dist/editable-artifact-live/server.d.ts +5 -2
  92. package/dist/editable-artifact-live/ticket.d.ts +2 -2
  93. package/dist/editable-artifact-live/types.d.ts +14 -2
  94. package/dist/editable-artifact-live.js +2 -2
  95. package/dist/editable-artifacts.js +10 -4
  96. package/dist/index.d.ts +49 -3
  97. package/dist/index.js +18812 -6833
  98. package/dist/index.js.map +1 -1
  99. package/dist/managed-auth-session-sets.d.ts +131 -0
  100. package/dist/managed-auth-session-sets.js +51 -0
  101. package/dist/managed-auth-session-sets.js.map +1 -0
  102. package/dist/managed-session.d.ts +98 -11
  103. package/dist/model-catalog.d.ts +117 -0
  104. package/dist/product-usage-metrics.d.ts +30 -0
  105. package/dist/rigs/index.d.ts +22 -5
  106. package/dist/rigs/provider-images.d.ts +1 -1
  107. package/dist/sandbox/fleet.d.ts +59 -10
  108. package/dist/sandbox/routing.d.ts +22 -2
  109. package/dist/sandbox/runtime-settings.d.ts +56 -0
  110. package/dist/session-authorization.d.ts +38 -1
  111. package/dist/site-session-origin.d.ts +9 -0
  112. package/dist/transcription.d.ts +12 -1
  113. package/dist/turn-surface.d.ts +18 -0
  114. package/dist/user-presence.d.ts +32 -0
  115. package/package.json +18 -11
  116. package/src/access/external-actor-authority.ts +94 -0
  117. package/src/access/index.ts +1386 -44
  118. package/src/access/service-initiator.ts +50 -0
  119. package/src/application/api-integration-servers.ts +215 -0
  120. package/src/application/archived-session-imports.ts +161 -0
  121. package/src/application/composer-submit.ts +77 -0
  122. package/src/application/connect-authority.ts +77 -0
  123. package/src/application/connect-operation.ts +80 -0
  124. package/src/application/external-continuation.ts +129 -0
  125. package/src/application/external-identity-lifecycle.ts +48 -0
  126. package/src/application/external-link-work-admission.ts +90 -0
  127. package/src/application/external-workspace-members.ts +232 -0
  128. package/src/application/modal-native-worker-host-transport.ts +263 -0
  129. package/src/application/new-session-drafts.ts +222 -29
  130. package/src/application/sandbox-recovery.ts +78 -0
  131. package/src/application/session-commands.ts +430 -73
  132. package/src/application/session-mcp-credential-rotation.ts +217 -0
  133. package/src/application/session-tenancy.ts +445 -0
  134. package/src/application/user-resource-grants.ts +154 -0
  135. package/src/billing/limits.ts +103 -24
  136. package/src/canonical-human-identities.ts +22 -3
  137. package/src/codex-model-availability.ts +136 -0
  138. package/src/default-session-model.ts +656 -0
  139. package/src/dependencies.ts +97 -5
  140. package/src/developer-setup-credential-ceiling.ts +60 -0
  141. package/src/domain/agent-config-resolution.ts +148 -0
  142. package/src/domain/automations.ts +270 -0
  143. package/src/domain/capabilities.ts +244 -151
  144. package/src/domain/company-profile-agent-admin.ts +56 -0
  145. package/src/domain/connection-account-selection-error.ts +19 -0
  146. package/src/domain/connector-tool-permissions.ts +371 -0
  147. package/src/domain/direct-model-provider.ts +62 -0
  148. package/src/domain/editable-artifacts/agent-application.ts +61 -15
  149. package/src/domain/editable-artifacts/durable-export.ts +100 -5
  150. package/src/domain/editable-artifacts/in-memory.ts +6 -0
  151. package/src/domain/editable-artifacts/ports.ts +11 -0
  152. package/src/domain/editable-artifacts/service.ts +32 -1
  153. package/src/domain/editable-artifacts/snapshot-compaction.ts +9 -5
  154. package/src/domain/editable-artifacts/snapshot-verifier-genesis.ts +10 -8
  155. package/src/domain/editable-artifacts/snapshot-verifier.ts +10 -3
  156. package/src/domain/environments.ts +25 -45
  157. package/src/domain/external-creation-attribution.ts +22 -0
  158. package/src/domain/file-owner.ts +67 -0
  159. package/src/domain/github-action-policies.ts +169 -0
  160. package/src/domain/github-repository-bindings.ts +295 -0
  161. package/src/domain/github-skill-source.ts +233 -0
  162. package/src/domain/governed-learning-slack-publication.ts +157 -0
  163. package/src/domain/insights-timing.ts +95 -0
  164. package/src/domain/insights-usage.ts +67 -0
  165. package/src/domain/insights.ts +270 -169
  166. package/src/domain/knowledge-files.ts +46 -0
  167. package/src/domain/knowledge-messages.test.ts +91 -0
  168. package/src/domain/knowledge-messages.ts +106 -0
  169. package/src/domain/knowledge-preparation.test.ts +276 -0
  170. package/src/domain/knowledge-preparation.ts +147 -0
  171. package/src/domain/knowledge-search.test.ts +561 -0
  172. package/src/domain/knowledge-search.ts +256 -0
  173. package/src/domain/knowledge.ts +133 -0
  174. package/src/domain/mcp-account-bindings.ts +171 -0
  175. package/src/domain/mcp-account-routes.ts +121 -0
  176. package/src/domain/native-mcp-connection-admission.ts +14 -0
  177. package/src/domain/organization-integration-catalog.ts +88 -0
  178. package/src/domain/organization-membership-lifecycle.ts +10 -0
  179. package/src/domain/personal-connection-delegations.ts +758 -115
  180. package/src/domain/pr-review.ts +687 -0
  181. package/src/domain/resources.ts +182 -25
  182. package/src/domain/scheduled-task-access.ts +1052 -0
  183. package/src/domain/scheduled-tasks.ts +1952 -215
  184. package/src/domain/session-connection-accounts.ts +67 -0
  185. package/src/domain/session-file-access.ts +47 -0
  186. package/src/domain/session-native-connection-replacement.ts +64 -0
  187. package/src/domain/session-start-timing.ts +55 -0
  188. package/src/domain/session-tool-policy.ts +443 -19
  189. package/src/domain/sessions.ts +3417 -619
  190. package/src/domain/skill-imports.ts +137 -17
  191. package/src/domain/skill-search.ts +221 -0
  192. package/src/domain/skills.ts +104 -0
  193. package/src/domain/slack-bot.ts +128 -3
  194. package/src/domain/workspace-members.ts +34 -2
  195. package/src/editable-artifact-live/application.ts +29 -4
  196. package/src/editable-artifact-live/ports.ts +0 -1
  197. package/src/editable-artifact-live/server.ts +167 -13
  198. package/src/editable-artifact-live/ticket.ts +87 -6
  199. package/src/editable-artifact-live/types.ts +16 -2
  200. package/src/editable-artifact-live/wire.ts +0 -10
  201. package/src/index.ts +69 -4
  202. package/src/managed-auth-session-sets.ts +371 -0
  203. package/src/managed-session.ts +520 -2
  204. package/src/model-catalog.ts +873 -0
  205. package/src/product-usage-metrics.ts +101 -0
  206. package/src/rigs/index.ts +179 -63
  207. package/src/rigs/provider-images.ts +2 -3
  208. package/src/sandbox/fleet.ts +471 -59
  209. package/src/sandbox/routing.ts +160 -33
  210. package/src/sandbox/runtime-settings.ts +281 -0
  211. package/src/session-authorization.ts +262 -78
  212. package/src/site-session-origin.ts +22 -0
  213. package/src/transcription.ts +13 -1
  214. package/src/turn-surface.ts +45 -0
  215. package/src/user-presence.ts +134 -0
  216. package/dist/chunk-6WKPWE5S.js.map +0 -1
  217. package/dist/chunk-IBOEYG6N.js +0 -34
  218. package/dist/chunk-IBOEYG6N.js.map +0 -1
  219. package/dist/chunk-JJU6I5XD.js.map +0 -1
  220. package/dist/chunk-NYPMQ7NO.js.map +0 -1
  221. package/dist/domain/durable-learning-slack-publication.d.ts +0 -72
  222. package/dist/domain/packs.d.ts +0 -55
  223. package/src/domain/durable-learning-slack-publication.ts +0 -202
  224. package/src/domain/packs.ts +0 -556
@@ -1,7 +1,10 @@
1
1
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
2
2
  import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
3
3
  import type { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
4
+ import { isDeepStrictEqual } from "node:util";
5
+ import { withLockedCapabilityInstallation } from "@opengeni/db/capability-reconciliation";
4
6
  import { environmentsEncryptionKeyBytes, type Settings } from "@opengeni/config";
7
+ import { pinnedFetch } from "@opengeni/network";
5
8
  import {
6
9
  CapabilityCatalogItem,
7
10
  capabilityCatalogItemIsTrustedForExposure,
@@ -16,6 +19,7 @@ import {
16
19
  type EnableCapabilityRequest,
17
20
  type McpServerConnectionRef,
18
21
  type McpPersonalConnectionDelegation,
22
+ assertOrganizationIntegrationAllowed,
19
23
  type SocialConnection,
20
24
  } from "@opengeni/contracts";
21
25
  import {
@@ -42,7 +46,6 @@ import {
42
46
  listEnabledMcpCapabilityServers,
43
47
  listInstalledApiIntegrations,
44
48
  listInstalledSkills,
45
- listPackInstallations,
46
49
  listSocialConnections,
47
50
  mcpServerIdForCapability,
48
51
  upsertCapabilityCatalogItem,
@@ -52,10 +55,14 @@ import {
52
55
  type InstalledSkillSummary,
53
56
  } from "@opengeni/db";
54
57
  import { HTTPException } from "hono/http-exception";
58
+ import {
59
+ withOrganizationIntegrationAcquisition,
60
+ withOrganizationIntegrationPolicyFence,
61
+ } from "@opengeni/db/organization-integration-policy";
55
62
  import { hasPermission } from "../access";
56
63
  import { isFikenConnection, preferredFikenConnection } from "./fiken";
57
64
  import { listSkillLibraryEntries, type SkillLibraryEntry } from "@opengeni/runtime/skill-library";
58
- import { listCapabilityPacks, listWorkspaceCapabilityPacks } from "./packs";
65
+ import { assertNativeMcpConnectionRef } from "./native-mcp-connection-admission";
59
66
 
60
67
  const officialMcpRegistryUrl = "https://registry.modelcontextprotocol.io";
61
68
  const firstPartyMcpServerIds = new Set(["opengeni", "files", "docs"]);
@@ -64,7 +71,6 @@ const mcpRegistryMaxPages = 3;
64
71
  const mcpCapabilityProbeTimeoutMs = 15000;
65
72
  const maxMcpCredentialHeaders = 16;
66
73
  const maxMcpCredentialHeaderValueLength = 4096;
67
- const officialGmailMcpUrl = "https://gmailmcp.googleapis.com/mcp/v1";
68
74
  // RFC 9110 field-name token characters.
69
75
  const mcpCredentialHeaderName = /^[A-Za-z0-9!#$%&'*+.^_`|~-]+$/;
70
76
 
@@ -77,18 +83,15 @@ export async function buildCapabilityCatalog(input: {
77
83
  const [
78
84
  persistedItems,
79
85
  capabilityInstallations,
80
- packInstallations,
81
- workspacePacks,
82
86
  socialConnections,
83
87
  workspaceConnections,
84
88
  curatedLibrarySkills,
85
89
  installedSkills,
86
90
  codexAppsCredentialId,
91
+ runnableMcpServers,
87
92
  ] = await Promise.all([
88
93
  listCapabilityCatalogItems(input.db, input.workspaceId),
89
94
  listCapabilityInstallations(input.db, input.workspaceId),
90
- listPackInstallations(input.db, input.workspaceId),
91
- listWorkspaceCapabilityPacks(input.db, input.workspaceId),
92
95
  listSocialConnections(input.db, input.workspaceId, 500, input.subjectId),
93
96
  listConnectionsMetadata(input.db, input.workspaceId, null),
94
97
  discoverCuratedSkillLibraryItems(),
@@ -96,7 +99,9 @@ export async function buildCapabilityCatalog(input: {
96
99
  input.settings.codexConnectedAppsEnabled
97
100
  ? resolveCodexAppsCredentialIdForRun(input.db, input.workspaceId)
98
101
  : Promise.resolve(null),
102
+ listEnabledMcpCapabilityServers(input.db, input.workspaceId),
99
103
  ]);
104
+ const runnableCapabilityIds = new Set(runnableMcpServers.map((server) => server.capabilityId));
100
105
  const catalogInstallations = capabilityInstallations.filter(
101
106
  (installation) => installation.kind === "mcp",
102
107
  );
@@ -108,20 +113,11 @@ export async function buildCapabilityCatalog(input: {
108
113
  .filter((skill) => skill.owners.some((owner) => owner.kind === "direct"))
109
114
  .map((skill) => [skill.capabilityId, skill]),
110
115
  );
111
- const activePackIds = new Set(
112
- packInstallations
113
- .filter((installation) => installation.status === "active")
114
- .map((installation) => installation.packId),
115
- );
116
- const builtInPackIds = new Set(listCapabilityPacks().map((pack) => pack.id));
117
116
  const builtIns = [
118
- ...workspacePacks.map((pack) =>
119
- packCatalogItem(pack, builtInPackIds.has(pack.id) ? "built_in" : "manual"),
120
- ),
121
117
  ...configuredMcpCatalogItems(input.settings),
122
118
  ...providerIntegrationCatalogItems(socialConnections),
123
119
  fikenCatalogItem(workspaceConnections.filter(isFikenConnection)),
124
- ...curatedLibrarySkills,
120
+ ...curatedLibrarySkills.filter((item) => installedSkillById.has(item.id)),
125
121
  ...installedSkills
126
122
  .filter(
127
123
  (skill) =>
@@ -149,8 +145,26 @@ export async function buildCapabilityCatalog(input: {
149
145
  const projected =
150
146
  item.kind === "skill"
151
147
  ? applyInstalledSkillEnablement(item, installedSkillById.get(item.id))
152
- : applyCapabilityEnablement(item, capabilityInstallationById.get(item.id), activePackIds);
153
- return applyCapabilityLifecycle(projected);
148
+ : applyCapabilityEnablement(item, capabilityInstallationById.get(item.id));
149
+ // An installed connector can become unrunnable after a lifecycle or
150
+ // ownership change. Use the execution registry rather than presenting
151
+ // its stale catalog definition as a selectable tool.
152
+ const runtimeProjected =
153
+ projected.kind === "mcp" &&
154
+ projected.enabled &&
155
+ projected.source !== "configured" &&
156
+ projected.source !== "built_in" &&
157
+ !runnableCapabilityIds.has(projected.id)
158
+ ? {
159
+ ...projected,
160
+ runtime: {
161
+ ...projected.runtime,
162
+ available: false,
163
+ notes: "This installed connection is unavailable. Review its connection settings.",
164
+ },
165
+ }
166
+ : projected;
167
+ return applyCapabilityLifecycle(runtimeProjected);
154
168
  })
155
169
  .sort(compareCatalogItems);
156
170
  return {
@@ -166,11 +180,7 @@ export async function createCatalogItem(input: {
166
180
  payload: CreateCapabilityCatalogItemRequest;
167
181
  }): Promise<CapabilityCatalogItem> {
168
182
  const id = input.payload.id?.trim() || generatedCapabilityId(input.payload);
169
- if (id.startsWith("pack:")) {
170
- throw new HTTPException(422, {
171
- message: "packs are managed by OpenGeni and cannot be manually created",
172
- });
173
- }
183
+
174
184
  if (id.startsWith("skill:")) {
175
185
  throw new HTTPException(422, {
176
186
  message: "Skills are installed through the Skill library or source import flow",
@@ -229,7 +239,7 @@ export async function createCatalogItem(input: {
229
239
  });
230
240
  }
231
241
 
232
- export async function enableCapability(input: {
242
+ type EnableCapabilityInput = {
233
243
  db: Database;
234
244
  grant: AccessGrant;
235
245
  accountId: string;
@@ -238,13 +248,29 @@ export async function enableCapability(input: {
238
248
  capabilityId: string;
239
249
  payload: EnableCapabilityRequest;
240
250
  probeMcpServer?: McpCapabilityProbe;
241
- }): Promise<CapabilityInstallation> {
251
+ };
252
+
253
+ export async function enableCapability(
254
+ input: EnableCapabilityInput,
255
+ ): Promise<CapabilityInstallation> {
256
+ const prepared = await prepareCapabilityEnable(input);
257
+ return prepared.commit(input.db);
258
+ }
259
+
260
+ /** Probe outside a durable Connect commit; persist the exact prepared settings
261
+ * inside the caller's authorized receipt transaction. Native enable uses this too. */
262
+ export async function prepareCapabilityEnable(input: EnableCapabilityInput) {
263
+ input = { ...input, payload: structuredClone(input.payload) };
242
264
  const item = await requireCatalogItem(
243
265
  input.db,
244
266
  input.workspaceId,
245
267
  input.settings,
246
268
  input.capabilityId,
247
269
  );
270
+ if (isReservedCodexAppsCatalogItem(item))
271
+ throw new HTTPException(422, {
272
+ message: "Codex Apps use the dedicated account designation flow",
273
+ });
248
274
  if (item.kind === "skill") {
249
275
  throw new HTTPException(409, {
250
276
  message: "Install Skills through the Skill library or source import flow",
@@ -260,11 +286,7 @@ export async function enableCapability(input: {
260
286
  message: "Install Plugins through the Plugin Package flow",
261
287
  });
262
288
  }
263
- if (item.kind === "pack") {
264
- throw new HTTPException(409, {
265
- message: "Install Packs through the Pack installation preview flow",
266
- });
267
- }
289
+
268
290
  if (item.kind === "mcp" && !item.runtime.available) {
269
291
  throw new HTTPException(422, {
270
292
  message: "MCP capabilities need a remote streamable HTTP endpoint before they can be enabled",
@@ -280,6 +302,49 @@ export async function enableCapability(input: {
280
302
  delete installationConfig.headerNames;
281
303
  delete installationConfig.connectionRef;
282
304
  if (item.kind === "mcp") {
305
+ if (
306
+ item.endpointUrl?.replace(/\/+$/u, "") === "https://gmailmcp.googleapis.com/mcp/v1" &&
307
+ (input.payload.connectionRef?.subjectScope !== "subject" ||
308
+ Object.keys(input.payload.headers ?? {}).length > 0)
309
+ ) {
310
+ throw new HTTPException(422, {
311
+ message: "Gmail requires a personal-owned connection reference",
312
+ });
313
+ }
314
+ // This generic, potentially workspace-authored catalog is not proof of a
315
+ // curated provider identity. Dedicated Connect adapters classify separately.
316
+ const unchanged = await withOrganizationIntegrationPolicyFence(
317
+ input.db,
318
+ input,
319
+ async (tx, policy) => {
320
+ const existing = await unchangedMcpInstallation(
321
+ { ...input, db: tx },
322
+ item,
323
+ installationConfig,
324
+ );
325
+ if (existing) return existing;
326
+ assertOrganizationIntegrationAllowed(policy, "custom:mcp");
327
+ return null;
328
+ },
329
+ );
330
+ if (unchanged) {
331
+ return {
332
+ commit: (db: Database) =>
333
+ withOrganizationIntegrationPolicyFence(db, input, async (tx) => {
334
+ const current = await unchangedMcpInstallation(
335
+ { ...input, db: tx },
336
+ item,
337
+ installationConfig,
338
+ );
339
+ if (!current || !isDeepStrictEqual(current, unchanged)) {
340
+ throw new HTTPException(409, {
341
+ message: "Capability installation changed; reload before reconciling",
342
+ });
343
+ }
344
+ return current;
345
+ }),
346
+ };
347
+ }
283
348
  const headers = await resolveMcpCredentialHeaders(input, item);
284
349
  const connectionRef = input.payload.connectionRef
285
350
  ? await validateMcpCapabilityConnectionRef(input, item, input.payload.connectionRef)
@@ -289,7 +354,12 @@ export async function enableCapability(input: {
289
354
  ...installationMetadata,
290
355
  ...(connectionRef && !headers
291
356
  ? authDeferredMcpConnectivity()
292
- : await validateMcpCapabilityConnection(item, input.probeMcpServer, headers ?? undefined)),
357
+ : await validateMcpCapabilityConnection(
358
+ item,
359
+ input.probeMcpServer,
360
+ headers ?? undefined,
361
+ input.settings,
362
+ )),
293
363
  };
294
364
  if (connectionRef) {
295
365
  installationConfig.connectionRef = connectionRef;
@@ -301,14 +371,66 @@ export async function enableCapability(input: {
301
371
  );
302
372
  }
303
373
  }
304
- return await enableCapabilityInstallation(input.db, {
374
+ const installation = {
305
375
  accountId: input.accountId,
306
376
  workspaceId: input.workspaceId,
307
377
  capabilityId: item.id,
308
378
  kind: item.kind,
309
379
  config: installationConfig,
310
380
  metadata: installationMetadata,
311
- });
381
+ ...(input.payload.onlyIfUninstalled ? { onlyIfUninstalled: true } : {}),
382
+ };
383
+ return {
384
+ commit: (db: Database) =>
385
+ item.kind === "mcp"
386
+ ? withOrganizationIntegrationAcquisition(db, installation, ["custom:mcp"], (tx) =>
387
+ enableCapabilityInstallation(tx, installation),
388
+ )
389
+ : enableCapabilityInstallation(db, installation),
390
+ };
391
+ }
392
+
393
+ /** A no-effect reconciliation is not a new acquisition. Validate ordinary
394
+ * connection visibility before locking the installation, and compare actual
395
+ * stored credentials rather than trusting caller-supplied identity hints. */
396
+ async function unchangedMcpInstallation(
397
+ input: EnableCapabilityInput,
398
+ item: CapabilityCatalogItem,
399
+ requestedConfig: Record<string, unknown>,
400
+ ): Promise<CapabilityInstallation | null> {
401
+ const connectionRef = input.payload.connectionRef
402
+ ? await validateMcpCapabilityConnectionRef(input, item, input.payload.connectionRef)
403
+ : null;
404
+ const existing = await withLockedCapabilityInstallation(
405
+ input.db,
406
+ input.workspaceId,
407
+ item.id,
408
+ (tx) => getCapabilityInstallation(tx, input.workspaceId, item.id),
409
+ );
410
+ if (!existing || existing.kind !== "mcp" || existing.status !== "active") return null;
411
+ const {
412
+ headerNames: _names,
413
+ headersEncrypted: _encrypted,
414
+ headers: _headers,
415
+ ...storedConfig
416
+ } = existing.config;
417
+ const config = { ...requestedConfig, ...(connectionRef ? { connectionRef } : {}) };
418
+ const { mcpConnectivity: _storedConnectivity, ...storedMetadata } = existing.metadata;
419
+ const { mcpConnectivity: _requestedConnectivity, ...requestedMetadata } = input.payload.metadata;
420
+ if (
421
+ !isDeepStrictEqual(config, storedConfig) ||
422
+ !isDeepStrictEqual(requestedMetadata, storedMetadata)
423
+ )
424
+ return null;
425
+ const provided = normalizedMcpCredentialHeaders(input.payload.headers);
426
+ if (provided) {
427
+ const stored = await resolveMcpCredentialHeaders(
428
+ { ...input, payload: { ...input.payload, headers: {} } },
429
+ item,
430
+ );
431
+ if (!isDeepStrictEqual(provided, stored)) return null;
432
+ }
433
+ return existing;
312
434
  }
313
435
 
314
436
  /**
@@ -402,24 +524,17 @@ function normalizedMcpCredentialHeaders(
402
524
  }
403
525
 
404
526
  async function validateMcpCapabilityConnectionRef(
405
- input: { db: Database; grant: AccessGrant; workspaceId: string },
527
+ input: { db: Database; grant: AccessGrant; workspaceId: string; settings: Settings },
406
528
  item: CapabilityCatalogItem,
407
529
  ref: McpServerConnectionRef,
408
530
  ): Promise<McpServerConnectionRef> {
409
531
  const subjectScope = ref.subjectScope ?? "workspace";
410
- const personalOnly =
411
- item.metadata.connectionOwnership === "personal_only" ||
412
- item.endpointUrl?.replace(/\/+$/, "") === officialGmailMcpUrl;
413
- if (personalOnly && subjectScope !== "subject") {
414
- throw new HTTPException(422, {
415
- message:
416
- "this capability requires a personal connection; each workspace member must connect their own account",
417
- });
418
- }
419
532
  const normalized: McpServerConnectionRef = {
420
533
  providerDomain: ref.providerDomain.trim(),
421
534
  subjectScope,
422
535
  ...(ref.connectionId ? { connectionId: ref.connectionId } : {}),
536
+ ...(ref.accountSelection ? { accountSelection: ref.accountSelection } : {}),
537
+ ...(ref.authoritySource === "host" ? { authoritySource: "host" as const } : {}),
423
538
  ...(ref.provider ? { provider: ref.provider.trim() } : {}),
424
539
  ...(ref.kind ? { kind: ref.kind } : {}),
425
540
  ...(ref.scopes ? { scopes: uniqueStrings(ref.scopes) } : {}),
@@ -443,6 +558,10 @@ async function validateMcpCapabilityConnectionRef(
443
558
  "MCP capabilities need a remote streamable HTTP endpoint before they can use a connectionRef",
444
559
  });
445
560
  }
561
+ if (normalized.authoritySource === "host") {
562
+ assertNativeMcpConnectionRef(normalized);
563
+ return normalized;
564
+ }
446
565
 
447
566
  let connection = normalized.connectionId
448
567
  ? await getConnectionMetadata(
@@ -452,7 +571,11 @@ async function validateMcpCapabilityConnectionRef(
452
571
  input.grant.subjectId,
453
572
  )
454
573
  : null;
455
- if (!connection && subjectScope === "subject" && !normalized.connectionId) {
574
+ if (
575
+ !connection &&
576
+ !normalized.connectionId &&
577
+ (subjectScope === "subject" || normalized.accountSelection === "all_eligible")
578
+ ) {
456
579
  const visible = await listConnectionsMetadata(
457
580
  input.db,
458
581
  input.workspaceId,
@@ -461,7 +584,7 @@ async function validateMcpCapabilityConnectionRef(
461
584
  connection =
462
585
  visible.find(
463
586
  (candidate) =>
464
- candidate.subjectId === input.grant.subjectId &&
587
+ candidate.subjectId === (subjectScope === "subject" ? input.grant.subjectId : null) &&
465
588
  candidate.providerDomain === normalized.providerDomain &&
466
589
  (!normalized.kind || candidate.kind === normalized.kind) &&
467
590
  candidate.status === "active",
@@ -561,6 +684,7 @@ export type McpCapabilityProbeInput = {
561
684
  url: string;
562
685
  timeoutMs: number;
563
686
  headers?: Record<string, string>;
687
+ settings?: Settings;
564
688
  };
565
689
 
566
690
  export type McpCapabilityProbeResult = {
@@ -575,6 +699,7 @@ export async function validateMcpCapabilityConnection(
575
699
  item: CapabilityCatalogItem,
576
700
  probe: McpCapabilityProbe = probeStreamableHttpMcpServer,
577
701
  headers?: Record<string, string>,
702
+ settings?: Settings,
578
703
  ): Promise<Record<string, unknown>> {
579
704
  if (item.kind !== "mcp") {
580
705
  return {};
@@ -591,6 +716,7 @@ export async function validateMcpCapabilityConnection(
591
716
  url: item.endpointUrl,
592
717
  timeoutMs: mcpCapabilityProbeTimeoutMs,
593
718
  ...(headers ? { headers } : {}),
719
+ ...(settings ? { settings } : {}),
594
720
  });
595
721
  return {
596
722
  mcpConnectivity: {
@@ -609,6 +735,8 @@ export async function validateMcpCapabilityConnection(
609
735
  async function probeStreamableHttpMcpServer(
610
736
  input: McpCapabilityProbeInput,
611
737
  ): Promise<McpCapabilityProbeResult> {
738
+ const settings = input.settings;
739
+ if (!settings) throw new Error("Network policy is unavailable for MCP validation");
612
740
  const controller = new AbortController();
613
741
  const timeout = setTimeout(() => controller.abort(), input.timeoutMs);
614
742
  const client = new Client(
@@ -621,6 +749,11 @@ async function probeStreamableHttpMcpServer(
621
749
  signal: controller.signal,
622
750
  ...(input.headers ? { headers: input.headers } : {}),
623
751
  },
752
+ fetch: (url, init) =>
753
+ pinnedFetch(url, init, settings, {
754
+ label: "MCP capability probe",
755
+ requireHttpsOutsideLocalTest: true,
756
+ }),
624
757
  });
625
758
  await client.connect(transport as unknown as Transport, {
626
759
  timeout: input.timeoutMs,
@@ -646,9 +779,9 @@ function mcpProbeErrorMessage(error: unknown, endpointUrl: string): string {
646
779
  normalized,
647
780
  )
648
781
  ) {
649
- return `OpenGeni could not reach a valid Streamable HTTP MCP server at ${endpoint}. Check the endpoint URL or choose a different catalog entry.`;
782
+ return `Opengeni could not reach a valid Streamable HTTP MCP server at ${endpoint}. Check the endpoint URL or choose a different catalog entry.`;
650
783
  }
651
- return `OpenGeni could not initialize ${endpoint}. Check the endpoint configuration or try again.`;
784
+ return `Opengeni could not initialize ${endpoint}. Check the endpoint configuration or try again.`;
652
785
  }
653
786
 
654
787
  function safeEndpointLabel(endpointUrl: string): string {
@@ -687,11 +820,7 @@ export async function disableCapability(input: {
687
820
  message: "Remove Plugins through the Plugin Package flow",
688
821
  });
689
822
  }
690
- if (item.kind === "pack") {
691
- throw new HTTPException(409, {
692
- message: "Uninstall Packs through the Pack uninstall preview flow",
693
- });
694
- }
823
+
695
824
  if (item.source === "built_in" || item.source === "configured") {
696
825
  throw new HTTPException(409, {
697
826
  message:
@@ -745,7 +874,7 @@ export function apiIntegrationsMatchingDelegations(
745
874
  const exact = new Set(
746
875
  delegations.map((delegation) =>
747
876
  [
748
- delegation.serverId,
877
+ delegation.canonicalServerId ?? delegation.serverId,
749
878
  delegation.connectionId,
750
879
  delegation.providerDomain.toLowerCase(),
751
880
  delegation.kind ?? "",
@@ -952,9 +1081,6 @@ export async function discoverMcpRegistryCapabilities(input: {
952
1081
  if (!item || seen.has(item.id)) {
953
1082
  continue;
954
1083
  }
955
- if (query && !catalogSearchText(item).includes(query)) {
956
- continue;
957
- }
958
1084
  seen.add(item.id);
959
1085
  items.push(item);
960
1086
  if (items.length >= limit) {
@@ -962,7 +1088,9 @@ export async function discoverMcpRegistryCapabilities(input: {
962
1088
  }
963
1089
  }
964
1090
  cursor = typeof page.metadata?.nextCursor === "string" ? page.metadata.nextCursor : undefined;
965
- if (!cursor) {
1091
+ // Return usable results promptly rather than fetching more pages merely
1092
+ // to fill the requested limit. The registry already applies the search.
1093
+ if (items.length > 0 || !cursor) {
966
1094
  break;
967
1095
  }
968
1096
  }
@@ -1028,52 +1156,6 @@ async function requireCatalogItem(
1028
1156
  return item;
1029
1157
  }
1030
1158
 
1031
- function packCatalogItem(
1032
- pack: ReturnType<typeof listCapabilityPacks>[number],
1033
- source: "built_in" | "manual",
1034
- ): CapabilityCatalogItem {
1035
- const customMetadata = { ...pack.metadata };
1036
- for (const key of [
1037
- "packId",
1038
- "version",
1039
- "connectors",
1040
- "knowledge",
1041
- "scheduledTaskTemplates",
1042
- "sandboxImage",
1043
- "sandboxProviderImages",
1044
- "skills",
1045
- ]) {
1046
- delete customMetadata[key];
1047
- }
1048
- return CapabilityCatalogItem.parse({
1049
- id: `pack:${pack.id}`,
1050
- kind: "pack",
1051
- source,
1052
- name: pack.name,
1053
- description: pack.description,
1054
- category: pack.category,
1055
- tags: [pack.role, pack.category, "pack"],
1056
- tools: pack.tools,
1057
- runtime: {
1058
- available: true,
1059
- notes: "Enables role-scoped tools, connectors, knowledge, and scheduled-task templates.",
1060
- },
1061
- metadata: {
1062
- ...customMetadata,
1063
- packId: pack.id,
1064
- version: pack.version,
1065
- connectors: pack.connectors,
1066
- knowledge: pack.knowledge,
1067
- scheduledTaskTemplates: pack.scheduledTaskTemplates,
1068
- ...(pack.variableSet ? { variableSet: pack.variableSet } : {}),
1069
- // Runtime composition surface only: skill names, never file content.
1070
- ...(pack.sandboxImage ? { sandboxImage: pack.sandboxImage } : {}),
1071
- ...(pack.sandboxProviderImages ? { sandboxProviderImages: pack.sandboxProviderImages } : {}),
1072
- ...(pack.skills.length > 0 ? { skills: pack.skills.map((skill) => skill.name) } : {}),
1073
- },
1074
- });
1075
- }
1076
-
1077
1159
  function configuredMcpCatalogItems(settings: Settings): CapabilityCatalogItem[] {
1078
1160
  return (
1079
1161
  settings.mcpServers
@@ -1094,12 +1176,23 @@ function configuredMcpCatalogItems(settings: Settings): CapabilityCatalogItem[]
1094
1176
  category: "configured",
1095
1177
  tags: ["mcp", ...(server.allowedTools?.length ? ["limited-tools"] : [])],
1096
1178
  endpointUrl: server.url,
1179
+ authKind:
1180
+ server.connectionRef?.authoritySource !== "host" &&
1181
+ server.connectionRef?.kind === "oauth2"
1182
+ ? "oauth2"
1183
+ : null,
1184
+ // Deployment-managed personal selectors need the same account picker
1185
+ // as installed connectors. Fixed bindings remain server-resolved.
1186
+ connectionRef:
1187
+ server.connectionRef?.subjectScope === "subject" && !server.connectionRef.connectionId
1188
+ ? installationConnectionRef({ connectionRef: server.connectionRef })
1189
+ : null,
1097
1190
  tools: [{ kind: "mcp", id: server.id }],
1098
1191
  runtime: {
1099
1192
  available: true,
1100
1193
  mcpServerId: server.id,
1101
1194
  transport: "streamable-http",
1102
- notes: "Managed by this OpenGeni deployment through OPENGENI_MCP_SERVERS.",
1195
+ notes: "Managed by this Opengeni deployment through OPENGENI_MCP_SERVERS.",
1103
1196
  },
1104
1197
  metadata: {
1105
1198
  mcpServerId: server.id,
@@ -1235,7 +1328,7 @@ function fikenCatalogItem(fikenConnections: ConnectionMetadata[]): CapabilityCat
1235
1328
  runtime: {
1236
1329
  available: true,
1237
1330
  mcpServerId: "opengeni",
1238
- notes: "Fiken access is provided through OpenGeni's first-party fiken tools.",
1331
+ notes: "Fiken access is provided through Opengeni's first-party fiken tools.",
1239
1332
  },
1240
1333
  enabled: fikenEnabled,
1241
1334
  enabledReason: fikenEnabled
@@ -1246,6 +1339,7 @@ function fikenCatalogItem(fikenConnections: ConnectionMetadata[]): CapabilityCat
1246
1339
  metadata: {
1247
1340
  connectorMode: "first_party_fiken",
1248
1341
  ownership: "workspace",
1342
+ connectionStatus: fikenConnection?.status ?? null,
1249
1343
  // Derived from the contracts catalog so a new fiken_* tool cannot be
1250
1344
  // registered without also appearing on the capability tile.
1251
1345
  firstPartyMcpTools: FIRST_PARTY_MCP_TOOL_NAMES.filter((name) => name.startsWith("fiken_")),
@@ -1277,11 +1371,11 @@ function providerIntegrationCatalogItems(
1277
1371
  available: true,
1278
1372
  mcpServerId: "opengeni",
1279
1373
  notes:
1280
- "OpenGeni's social provider adapter routes every call through an exact visible account Connection.",
1374
+ "Opengeni's social provider adapter routes every call through an exact visible account Connection.",
1281
1375
  },
1282
1376
  enabled,
1283
1377
  enabledReason: socialConnectionSummary(counts),
1284
- provenance: "OpenGeni provider adapter",
1378
+ provenance: "Opengeni provider adapter",
1285
1379
  metadata: {
1286
1380
  providerAdapter: "social",
1287
1381
  provider: definition.provider,
@@ -1306,7 +1400,7 @@ export function nativeConnectionCapabilityRecommendations(): CapabilityCatalogIt
1306
1400
  kind: "api",
1307
1401
  source: "built_in",
1308
1402
  name: "GitHub App",
1309
- description: "Connect repositories through OpenGeni's GitHub resource picker.",
1403
+ description: "Connect repositories through Opengeni's GitHub resource picker.",
1310
1404
  category: "source-control",
1311
1405
  tags: ["github", "repositories", "source-control"],
1312
1406
  homepageUrl: "https://github.com",
@@ -1494,31 +1588,21 @@ function socialProviderConnectionCounts(item: CapabilityCatalogItem): {
1494
1588
  export function applyCapabilityEnablement(
1495
1589
  item: CapabilityCatalogItem,
1496
1590
  installation: CapabilityInstallation | undefined,
1497
- activePackIds: Set<string>,
1498
1591
  ): CapabilityCatalogItem {
1592
+ if (item.surfaceType === "first_party_fiken") {
1593
+ // Fiken is an API item with connection-derived state, not a generic API
1594
+ // installation. Preserve that state before the non-MCP fallback below.
1595
+ return { ...item, connectionRef: null };
1596
+ }
1499
1597
  if (item.kind === "skill" || item.kind === "api" || item.kind === "plugin") {
1500
1598
  return { ...item, enabled: false, enabledReason: null, connectionRef: null };
1501
1599
  }
1502
- if (item.kind === "pack") {
1503
- const enabled = activePackIds.has(packIdFromCapabilityId(item.id));
1504
- return {
1505
- ...item,
1506
- enabled,
1507
- enabledReason: enabled ? "enabled" : null,
1508
- };
1509
- }
1510
1600
  if (isSocialProviderIntegration(item)) {
1511
1601
  // Provider-integration state is derived from every authoritative visible
1512
1602
  // social Connection while the catalog is built. The catalog summary never
1513
1603
  // publishes a personal connection UUID or collapses many accounts to one.
1514
1604
  return { ...item, connectionRef: null };
1515
1605
  }
1516
- if (item.surfaceType === "first_party_fiken") {
1517
- // Fiken connector state is derived from the authoritative workspace
1518
- // connection row while the catalog is built; browseable never means an
1519
- // account is already connected.
1520
- return { ...item, connectionRef: null };
1521
- }
1522
1606
  if (item.surfaceType === "codex_apps") {
1523
1607
  // Unlike ordinary built-ins, Apps availability is derived from the exact
1524
1608
  // active workspace designation above. Do not overwrite it with the
@@ -1530,7 +1614,7 @@ export function applyCapabilityEnablement(
1530
1614
  ...item,
1531
1615
  enabled: true,
1532
1616
  enabledReason: "managed by deployment",
1533
- connectionRef: null,
1617
+ connectionRef: item.connectionRef,
1534
1618
  };
1535
1619
  }
1536
1620
  if (item.source === "built_in") {
@@ -1541,11 +1625,17 @@ export function applyCapabilityEnablement(
1541
1625
  }
1542
1626
  const activeInstallation = installation?.status === "active";
1543
1627
  const enabled = !!activeInstallation && capabilityInstallationRuntimeReady(item, installation);
1628
+ const connectionRef =
1629
+ enabled && installation ? installationConnectionRef(installation.config) : null;
1544
1630
  return {
1545
1631
  ...item,
1632
+ // Older custom entries recorded the native credential kind on the
1633
+ // installation only. Project it without guessing from a URL or overwriting
1634
+ // an explicit catalog authentication contract.
1635
+ authKind: item.authKind ?? (connectionRef?.kind === "oauth2" ? "oauth2" : null),
1546
1636
  enabled,
1547
1637
  enabledReason: enabled ? "enabled" : null,
1548
- connectionRef: enabled && installation ? installationConnectionRef(installation.config) : null,
1638
+ connectionRef,
1549
1639
  };
1550
1640
  }
1551
1641
 
@@ -1572,7 +1662,10 @@ function applyCapabilityLifecycle(item: CapabilityCatalogItem): CapabilityCatalo
1572
1662
  detail: item.runtime.notes,
1573
1663
  managedBy: item.source === "built_in" ? "platform" : null,
1574
1664
  },
1575
- actions: ["inspect"],
1665
+ actions:
1666
+ item.enabled && item.kind === "mcp" && item.source !== "built_in"
1667
+ ? ["disconnect", "inspect"]
1668
+ : ["inspect"],
1576
1669
  };
1577
1670
  }
1578
1671
 
@@ -1611,7 +1704,7 @@ function applyCapabilityLifecycle(item: CapabilityCatalogItem): CapabilityCatalo
1611
1704
 
1612
1705
  const installed = item.enabled;
1613
1706
  const actions: CapabilityAction[] = installed
1614
- ? item.kind === "pack" || item.kind === "skill" || item.kind === "plugin"
1707
+ ? item.kind === "skill" || item.kind === "plugin"
1615
1708
  ? ["configure", "update", "uninstall", "inspect"]
1616
1709
  : ["configure", "disconnect", "inspect"]
1617
1710
  : item.kind === "mcp" || item.kind === "api"
@@ -1674,14 +1767,34 @@ function installationConnectionRef(
1674
1767
  if (!ref || typeof ref !== "object") {
1675
1768
  return null;
1676
1769
  }
1677
- const { connectionId, providerDomain, kind, subjectScope } = ref as Record<string, unknown>;
1770
+ const { authoritySource, connectionId, providerDomain, kind, subjectScope, accountSelection } =
1771
+ ref as Record<string, unknown>;
1678
1772
  if (typeof providerDomain !== "string" || typeof kind !== "string") {
1679
1773
  return null;
1680
1774
  }
1775
+ if (authoritySource === "host") {
1776
+ // The internal installation/runtime ref retains the exact host binding.
1777
+ // Public capability catalogs use the existing null representation for an
1778
+ // enabled capability without a native OpenGeni connection, so indefinitely
1779
+ // open old browser bundles cannot treat a host UUID as native OAuth state.
1780
+ return null;
1781
+ }
1782
+ if (accountSelection === "all_eligible" && connectionId === undefined) {
1783
+ return {
1784
+ providerDomain,
1785
+ kind,
1786
+ accountSelection,
1787
+ ...(subjectScope === "subject" ? { subjectScope } : {}),
1788
+ };
1789
+ }
1681
1790
  if (subjectScope === "subject") {
1682
- // Never project a personal connection UUID through workspace-visible
1791
+ // Never project a native personal connection UUID through workspace-visible
1683
1792
  // capability configuration, including legacy rows that still contain one.
1684
- return { providerDomain, kind, subjectScope: "subject" };
1793
+ return {
1794
+ providerDomain,
1795
+ kind,
1796
+ subjectScope: "subject",
1797
+ };
1685
1798
  }
1686
1799
  if (typeof connectionId !== "string") {
1687
1800
  return null;
@@ -1807,10 +1920,6 @@ function publicRegistryCapabilityId(name: string, version: string, endpointUrl:
1807
1920
  return `mcp-registry:${slugify(name)}-${shortHash(`${name}:${version}:${endpointUrl}`)}`;
1808
1921
  }
1809
1922
 
1810
- function packIdFromCapabilityId(capabilityId: string): string {
1811
- return capabilityId.replace(/^pack:/, "");
1812
- }
1813
-
1814
1923
  function uniqueTags(tags: string[]): string[] {
1815
1924
  return [...new Set(tags.map((tag) => tag.trim()).filter(Boolean))];
1816
1925
  }
@@ -1958,22 +2067,6 @@ function validUrl(value: string | undefined): string | null {
1958
2067
  }
1959
2068
  }
1960
2069
 
1961
- function catalogSearchText(item: CapabilityCatalogItem): string {
1962
- return [
1963
- item.name,
1964
- item.description,
1965
- item.category,
1966
- ...item.tags,
1967
- item.endpointUrl,
1968
- item.homepageUrl,
1969
- item.installUrl,
1970
- JSON.stringify(item.metadata),
1971
- ]
1972
- .filter(Boolean)
1973
- .join(" ")
1974
- .toLowerCase();
1975
- }
1976
-
1977
2070
  function capabilityInstallationRuntimeReady(
1978
2071
  item: CapabilityCatalogItem,
1979
2072
  installation: CapabilityInstallation | undefined,