@opengeni/core 3.0.1 → 4.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.
- package/README.md +1 -1
- package/dist/application/connect-operation.d.ts +3 -0
- package/dist/application/sandbox-recovery.d.ts +49 -0
- package/dist/{chunk-H7X52RI2.js → chunk-TFHSE7XR.js} +13 -2
- package/dist/chunk-TFHSE7XR.js.map +1 -0
- package/dist/dependencies.d.ts +1 -1
- package/dist/domain/capabilities.d.ts +2 -2
- package/dist/domain/connection-account-selection-error.d.ts +6 -0
- package/dist/domain/connector-tool-permissions.d.ts +27 -0
- package/dist/domain/editable-artifacts/agent-application.d.ts +8 -0
- package/dist/domain/environments.d.ts +3 -7
- package/dist/domain/insights-timing.d.ts +16 -0
- package/dist/domain/insights.d.ts +4 -1
- package/dist/domain/knowledge-files.d.ts +1 -1
- package/dist/domain/knowledge-preparation.d.ts +9 -0
- package/dist/domain/knowledge-preparation.test.d.ts +1 -0
- package/dist/domain/mcp-account-bindings.d.ts +19 -0
- package/dist/domain/native-mcp-connection-admission.d.ts +3 -0
- package/dist/domain/organization-integration-catalog.d.ts +15 -0
- package/dist/domain/personal-connection-delegations.d.ts +19 -10
- package/dist/domain/pr-review.d.ts +56 -4
- package/dist/domain/scheduled-tasks.d.ts +8 -5
- package/dist/domain/session-file-access.d.ts +4 -0
- package/dist/domain/sessions.d.ts +8 -6
- package/dist/domain/skills.d.ts +29 -1
- package/dist/editable-artifacts.js +1 -1
- package/dist/index.d.ts +7 -5
- package/dist/index.js +4868 -4803
- package/dist/index.js.map +1 -1
- package/dist/sandbox/routing.d.ts +2 -1
- package/dist/sandbox/runtime-settings.d.ts +1 -15
- package/package.json +11 -19
- package/src/application/composer-submit.ts +2 -1
- package/src/application/connect-operation.ts +32 -3
- package/src/application/sandbox-recovery.ts +55 -0
- package/src/application/user-resource-grants.ts +1 -16
- package/src/dependencies.ts +5 -1
- package/src/domain/capabilities.ts +169 -122
- package/src/domain/connection-account-selection-error.ts +9 -0
- package/src/domain/connector-tool-permissions.ts +305 -0
- package/src/domain/editable-artifacts/agent-application.ts +31 -2
- package/src/domain/environments.ts +4 -9
- package/src/domain/file-owner.ts +16 -1
- package/src/domain/insights-timing.ts +95 -0
- package/src/domain/insights.ts +59 -35
- package/src/domain/knowledge-files.ts +10 -3
- package/src/domain/knowledge-messages.ts +1 -0
- package/src/domain/knowledge-preparation.test.ts +251 -0
- package/src/domain/knowledge-preparation.ts +126 -0
- package/src/domain/mcp-account-bindings.ts +152 -0
- package/src/domain/native-mcp-connection-admission.ts +14 -0
- package/src/domain/organization-integration-catalog.ts +83 -0
- package/src/domain/personal-connection-delegations.ts +226 -177
- package/src/domain/pr-review.ts +22 -6
- package/src/domain/resources.ts +9 -2
- package/src/domain/scheduled-tasks.ts +200 -155
- package/src/domain/session-file-access.ts +47 -0
- package/src/domain/session-tool-policy.ts +12 -4
- package/src/domain/sessions.ts +390 -261
- package/src/domain/skills.ts +9 -0
- package/src/index.ts +10 -6
- package/src/rigs/index.ts +17 -17
- package/src/sandbox/routing.ts +18 -7
- package/src/sandbox/runtime-settings.ts +7 -91
- package/dist/application/host-mcp-owner.d.ts +0 -6
- package/dist/application/host-mcp-resolvers.d.ts +0 -13
- package/dist/chunk-5WZYFL45.js +0 -266
- package/dist/chunk-5WZYFL45.js.map +0 -1
- package/dist/chunk-H7X52RI2.js.map +0 -1
- package/dist/domain/connection-authority.d.ts +0 -97
- package/dist/domain/connection-authority.js +0 -169
- package/dist/domain/connection-authority.js.map +0 -1
- package/dist/domain/host-mcp-authority-source-admission.d.ts +0 -10
- package/dist/domain/host-mcp-task-admission.d.ts +0 -18
- package/dist/domain/packs.d.ts +0 -59
- package/dist/domain/product-integration-pack.d.ts +0 -44
- package/dist/domain/product-integration-skill.gen.d.ts +0 -5
- package/dist/remote-mcp-credentials.d.ts +0 -23
- package/dist/remote-mcp-credentials.js +0 -11
- package/dist/remote-mcp-credentials.js.map +0 -1
- package/src/application/host-mcp-owner.ts +0 -41
- package/src/application/host-mcp-resolvers.ts +0 -84
- package/src/domain/connection-authority.ts +0 -329
- package/src/domain/host-mcp-authority-source-admission.ts +0 -25
- package/src/domain/host-mcp-task-admission.ts +0 -121
- package/src/domain/packs.ts +0 -699
- package/src/domain/product-integration-pack.ts +0 -42
- package/src/domain/product-integration-skill.gen.ts +0 -52
- package/src/remote-mcp-credentials.ts +0 -361
|
@@ -1,6 +1,8 @@
|
|
|
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";
|
|
5
7
|
import {
|
|
6
8
|
CapabilityCatalogItem,
|
|
@@ -16,7 +18,7 @@ import {
|
|
|
16
18
|
type EnableCapabilityRequest,
|
|
17
19
|
type McpServerConnectionRef,
|
|
18
20
|
type McpPersonalConnectionDelegation,
|
|
19
|
-
|
|
21
|
+
assertOrganizationIntegrationAllowed,
|
|
20
22
|
type SocialConnection,
|
|
21
23
|
} from "@opengeni/contracts";
|
|
22
24
|
import {
|
|
@@ -43,7 +45,6 @@ import {
|
|
|
43
45
|
listEnabledMcpCapabilityServers,
|
|
44
46
|
listInstalledApiIntegrations,
|
|
45
47
|
listInstalledSkills,
|
|
46
|
-
listPackInstallations,
|
|
47
48
|
listSocialConnections,
|
|
48
49
|
mcpServerIdForCapability,
|
|
49
50
|
upsertCapabilityCatalogItem,
|
|
@@ -53,11 +54,14 @@ import {
|
|
|
53
54
|
type InstalledSkillSummary,
|
|
54
55
|
} from "@opengeni/db";
|
|
55
56
|
import { HTTPException } from "hono/http-exception";
|
|
57
|
+
import {
|
|
58
|
+
withOrganizationIntegrationAcquisition,
|
|
59
|
+
withOrganizationIntegrationPolicyFence,
|
|
60
|
+
} from "@opengeni/db/organization-integration-policy";
|
|
56
61
|
import { hasPermission } from "../access";
|
|
57
62
|
import { isFikenConnection, preferredFikenConnection } from "./fiken";
|
|
58
63
|
import { listSkillLibraryEntries, type SkillLibraryEntry } from "@opengeni/runtime/skill-library";
|
|
59
|
-
import {
|
|
60
|
-
import { assertHostMcpAuthoritySourceAdmissionEnabled } from "./host-mcp-authority-source-admission";
|
|
64
|
+
import { assertNativeMcpConnectionRef } from "./native-mcp-connection-admission";
|
|
61
65
|
|
|
62
66
|
const officialMcpRegistryUrl = "https://registry.modelcontextprotocol.io";
|
|
63
67
|
const firstPartyMcpServerIds = new Set(["opengeni", "files", "docs"]);
|
|
@@ -66,7 +70,6 @@ const mcpRegistryMaxPages = 3;
|
|
|
66
70
|
const mcpCapabilityProbeTimeoutMs = 15000;
|
|
67
71
|
const maxMcpCredentialHeaders = 16;
|
|
68
72
|
const maxMcpCredentialHeaderValueLength = 4096;
|
|
69
|
-
const officialGmailMcpUrl = "https://gmailmcp.googleapis.com/mcp/v1";
|
|
70
73
|
// RFC 9110 field-name token characters.
|
|
71
74
|
const mcpCredentialHeaderName = /^[A-Za-z0-9!#$%&'*+.^_`|~-]+$/;
|
|
72
75
|
|
|
@@ -79,18 +82,15 @@ export async function buildCapabilityCatalog(input: {
|
|
|
79
82
|
const [
|
|
80
83
|
persistedItems,
|
|
81
84
|
capabilityInstallations,
|
|
82
|
-
packInstallations,
|
|
83
|
-
workspacePacks,
|
|
84
85
|
socialConnections,
|
|
85
86
|
workspaceConnections,
|
|
86
87
|
curatedLibrarySkills,
|
|
87
88
|
installedSkills,
|
|
88
89
|
codexAppsCredentialId,
|
|
90
|
+
runnableMcpServers,
|
|
89
91
|
] = await Promise.all([
|
|
90
92
|
listCapabilityCatalogItems(input.db, input.workspaceId),
|
|
91
93
|
listCapabilityInstallations(input.db, input.workspaceId),
|
|
92
|
-
listPackInstallations(input.db, input.workspaceId),
|
|
93
|
-
listWorkspaceCapabilityPacks(input.db, input.workspaceId),
|
|
94
94
|
listSocialConnections(input.db, input.workspaceId, 500, input.subjectId),
|
|
95
95
|
listConnectionsMetadata(input.db, input.workspaceId, null),
|
|
96
96
|
discoverCuratedSkillLibraryItems(),
|
|
@@ -98,7 +98,9 @@ export async function buildCapabilityCatalog(input: {
|
|
|
98
98
|
input.settings.codexConnectedAppsEnabled
|
|
99
99
|
? resolveCodexAppsCredentialIdForRun(input.db, input.workspaceId)
|
|
100
100
|
: Promise.resolve(null),
|
|
101
|
+
listEnabledMcpCapabilityServers(input.db, input.workspaceId),
|
|
101
102
|
]);
|
|
103
|
+
const runnableCapabilityIds = new Set(runnableMcpServers.map((server) => server.capabilityId));
|
|
102
104
|
const catalogInstallations = capabilityInstallations.filter(
|
|
103
105
|
(installation) => installation.kind === "mcp",
|
|
104
106
|
);
|
|
@@ -110,16 +112,7 @@ export async function buildCapabilityCatalog(input: {
|
|
|
110
112
|
.filter((skill) => skill.owners.some((owner) => owner.kind === "direct"))
|
|
111
113
|
.map((skill) => [skill.capabilityId, skill]),
|
|
112
114
|
);
|
|
113
|
-
const activePackIds = new Set(
|
|
114
|
-
packInstallations
|
|
115
|
-
.filter((installation) => installation.status === "active")
|
|
116
|
-
.map((installation) => installation.packId),
|
|
117
|
-
);
|
|
118
|
-
const builtInPackIds = new Set(listCapabilityPacks().map((pack) => pack.id));
|
|
119
115
|
const builtIns = [
|
|
120
|
-
...workspacePacks.map((pack) =>
|
|
121
|
-
packCatalogItem(pack, builtInPackIds.has(pack.id) ? "built_in" : "manual"),
|
|
122
|
-
),
|
|
123
116
|
...configuredMcpCatalogItems(input.settings),
|
|
124
117
|
...providerIntegrationCatalogItems(socialConnections),
|
|
125
118
|
fikenCatalogItem(workspaceConnections.filter(isFikenConnection)),
|
|
@@ -151,8 +144,26 @@ export async function buildCapabilityCatalog(input: {
|
|
|
151
144
|
const projected =
|
|
152
145
|
item.kind === "skill"
|
|
153
146
|
? applyInstalledSkillEnablement(item, installedSkillById.get(item.id))
|
|
154
|
-
: applyCapabilityEnablement(item, capabilityInstallationById.get(item.id)
|
|
155
|
-
|
|
147
|
+
: applyCapabilityEnablement(item, capabilityInstallationById.get(item.id));
|
|
148
|
+
// An installed connector can become unrunnable after a lifecycle or
|
|
149
|
+
// ownership change. Use the execution registry rather than presenting
|
|
150
|
+
// its stale catalog definition as a selectable tool.
|
|
151
|
+
const runtimeProjected =
|
|
152
|
+
projected.kind === "mcp" &&
|
|
153
|
+
projected.enabled &&
|
|
154
|
+
projected.source !== "configured" &&
|
|
155
|
+
projected.source !== "built_in" &&
|
|
156
|
+
!runnableCapabilityIds.has(projected.id)
|
|
157
|
+
? {
|
|
158
|
+
...projected,
|
|
159
|
+
runtime: {
|
|
160
|
+
...projected.runtime,
|
|
161
|
+
available: false,
|
|
162
|
+
notes: "This installed connection is unavailable. Review its connection settings.",
|
|
163
|
+
},
|
|
164
|
+
}
|
|
165
|
+
: projected;
|
|
166
|
+
return applyCapabilityLifecycle(runtimeProjected);
|
|
156
167
|
})
|
|
157
168
|
.sort(compareCatalogItems);
|
|
158
169
|
return {
|
|
@@ -168,11 +179,7 @@ export async function createCatalogItem(input: {
|
|
|
168
179
|
payload: CreateCapabilityCatalogItemRequest;
|
|
169
180
|
}): Promise<CapabilityCatalogItem> {
|
|
170
181
|
const id = input.payload.id?.trim() || generatedCapabilityId(input.payload);
|
|
171
|
-
|
|
172
|
-
throw new HTTPException(422, {
|
|
173
|
-
message: "packs are managed by OpenGeni and cannot be manually created",
|
|
174
|
-
});
|
|
175
|
-
}
|
|
182
|
+
|
|
176
183
|
if (id.startsWith("skill:")) {
|
|
177
184
|
throw new HTTPException(422, {
|
|
178
185
|
message: "Skills are installed through the Skill library or source import flow",
|
|
@@ -252,6 +259,7 @@ export async function enableCapability(
|
|
|
252
259
|
/** Probe outside a durable Connect commit; persist the exact prepared settings
|
|
253
260
|
* inside the caller's authorized receipt transaction. Native enable uses this too. */
|
|
254
261
|
export async function prepareCapabilityEnable(input: EnableCapabilityInput) {
|
|
262
|
+
input = { ...input, payload: structuredClone(input.payload) };
|
|
255
263
|
const item = await requireCatalogItem(
|
|
256
264
|
input.db,
|
|
257
265
|
input.workspaceId,
|
|
@@ -277,11 +285,7 @@ export async function prepareCapabilityEnable(input: EnableCapabilityInput) {
|
|
|
277
285
|
message: "Install Plugins through the Plugin Package flow",
|
|
278
286
|
});
|
|
279
287
|
}
|
|
280
|
-
|
|
281
|
-
throw new HTTPException(409, {
|
|
282
|
-
message: "Install Packs through the Pack installation preview flow",
|
|
283
|
-
});
|
|
284
|
-
}
|
|
288
|
+
|
|
285
289
|
if (item.kind === "mcp" && !item.runtime.available) {
|
|
286
290
|
throw new HTTPException(422, {
|
|
287
291
|
message: "MCP capabilities need a remote streamable HTTP endpoint before they can be enabled",
|
|
@@ -297,6 +301,40 @@ export async function prepareCapabilityEnable(input: EnableCapabilityInput) {
|
|
|
297
301
|
delete installationConfig.headerNames;
|
|
298
302
|
delete installationConfig.connectionRef;
|
|
299
303
|
if (item.kind === "mcp") {
|
|
304
|
+
// This generic, potentially workspace-authored catalog is not proof of a
|
|
305
|
+
// curated provider identity. Dedicated Connect adapters classify separately.
|
|
306
|
+
const unchanged = await withOrganizationIntegrationPolicyFence(
|
|
307
|
+
input.db,
|
|
308
|
+
input,
|
|
309
|
+
async (tx, policy) => {
|
|
310
|
+
const existing = await unchangedMcpInstallation(
|
|
311
|
+
{ ...input, db: tx },
|
|
312
|
+
item,
|
|
313
|
+
installationConfig,
|
|
314
|
+
);
|
|
315
|
+
if (existing) return existing;
|
|
316
|
+
assertOrganizationIntegrationAllowed(policy, "custom:mcp");
|
|
317
|
+
return null;
|
|
318
|
+
},
|
|
319
|
+
);
|
|
320
|
+
if (unchanged) {
|
|
321
|
+
return {
|
|
322
|
+
commit: (db: Database) =>
|
|
323
|
+
withOrganizationIntegrationPolicyFence(db, input, async (tx) => {
|
|
324
|
+
const current = await unchangedMcpInstallation(
|
|
325
|
+
{ ...input, db: tx },
|
|
326
|
+
item,
|
|
327
|
+
installationConfig,
|
|
328
|
+
);
|
|
329
|
+
if (!current || !isDeepStrictEqual(current, unchanged)) {
|
|
330
|
+
throw new HTTPException(409, {
|
|
331
|
+
message: "Capability installation changed; reload before reconciling",
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
return current;
|
|
335
|
+
}),
|
|
336
|
+
};
|
|
337
|
+
}
|
|
300
338
|
const headers = await resolveMcpCredentialHeaders(input, item);
|
|
301
339
|
const connectionRef = input.payload.connectionRef
|
|
302
340
|
? await validateMcpCapabilityConnectionRef(input, item, input.payload.connectionRef)
|
|
@@ -325,8 +363,59 @@ export async function prepareCapabilityEnable(input: EnableCapabilityInput) {
|
|
|
325
363
|
kind: item.kind,
|
|
326
364
|
config: installationConfig,
|
|
327
365
|
metadata: installationMetadata,
|
|
366
|
+
...(input.payload.onlyIfUninstalled ? { onlyIfUninstalled: true } : {}),
|
|
367
|
+
};
|
|
368
|
+
return {
|
|
369
|
+
commit: (db: Database) =>
|
|
370
|
+
item.kind === "mcp"
|
|
371
|
+
? withOrganizationIntegrationAcquisition(db, installation, ["custom:mcp"], (tx) =>
|
|
372
|
+
enableCapabilityInstallation(tx, installation),
|
|
373
|
+
)
|
|
374
|
+
: enableCapabilityInstallation(db, installation),
|
|
328
375
|
};
|
|
329
|
-
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/** A no-effect reconciliation is not a new acquisition. Validate ordinary
|
|
379
|
+
* connection visibility before locking the installation, and compare actual
|
|
380
|
+
* stored credentials rather than trusting caller-supplied identity hints. */
|
|
381
|
+
async function unchangedMcpInstallation(
|
|
382
|
+
input: EnableCapabilityInput,
|
|
383
|
+
item: CapabilityCatalogItem,
|
|
384
|
+
requestedConfig: Record<string, unknown>,
|
|
385
|
+
): Promise<CapabilityInstallation | null> {
|
|
386
|
+
const connectionRef = input.payload.connectionRef
|
|
387
|
+
? await validateMcpCapabilityConnectionRef(input, item, input.payload.connectionRef)
|
|
388
|
+
: null;
|
|
389
|
+
const existing = await withLockedCapabilityInstallation(
|
|
390
|
+
input.db,
|
|
391
|
+
input.workspaceId,
|
|
392
|
+
item.id,
|
|
393
|
+
(tx) => getCapabilityInstallation(tx, input.workspaceId, item.id),
|
|
394
|
+
);
|
|
395
|
+
if (!existing || existing.kind !== "mcp" || existing.status !== "active") return null;
|
|
396
|
+
const {
|
|
397
|
+
headerNames: _names,
|
|
398
|
+
headersEncrypted: _encrypted,
|
|
399
|
+
headers: _headers,
|
|
400
|
+
...storedConfig
|
|
401
|
+
} = existing.config;
|
|
402
|
+
const config = { ...requestedConfig, ...(connectionRef ? { connectionRef } : {}) };
|
|
403
|
+
const { mcpConnectivity: _storedConnectivity, ...storedMetadata } = existing.metadata;
|
|
404
|
+
const { mcpConnectivity: _requestedConnectivity, ...requestedMetadata } = input.payload.metadata;
|
|
405
|
+
if (
|
|
406
|
+
!isDeepStrictEqual(config, storedConfig) ||
|
|
407
|
+
!isDeepStrictEqual(requestedMetadata, storedMetadata)
|
|
408
|
+
)
|
|
409
|
+
return null;
|
|
410
|
+
const provided = normalizedMcpCredentialHeaders(input.payload.headers);
|
|
411
|
+
if (provided) {
|
|
412
|
+
const stored = await resolveMcpCredentialHeaders(
|
|
413
|
+
{ ...input, payload: { ...input.payload, headers: {} } },
|
|
414
|
+
item,
|
|
415
|
+
);
|
|
416
|
+
if (!isDeepStrictEqual(provided, stored)) return null;
|
|
417
|
+
}
|
|
418
|
+
return existing;
|
|
330
419
|
}
|
|
331
420
|
|
|
332
421
|
/**
|
|
@@ -425,24 +514,11 @@ async function validateMcpCapabilityConnectionRef(
|
|
|
425
514
|
ref: McpServerConnectionRef,
|
|
426
515
|
): Promise<McpServerConnectionRef> {
|
|
427
516
|
const subjectScope = ref.subjectScope ?? "workspace";
|
|
428
|
-
const endpointUrl = item.endpointUrl?.replace(/\/+$/, "");
|
|
429
|
-
// Gmail is a consumer mailbox; Slack's hosted MCP issues user tokens only and
|
|
430
|
-
// shared Slack authority belongs to the OpenGeni workspace bot. Neither may
|
|
431
|
-
// become a workspace-owned connection reference.
|
|
432
|
-
const personalOnly =
|
|
433
|
-
item.metadata.connectionOwnership === "personal_only" ||
|
|
434
|
-
endpointUrl === officialGmailMcpUrl ||
|
|
435
|
-
endpointUrl === OPENGENI_PERSONAL_SLACK_MCP_URL;
|
|
436
|
-
if (personalOnly && subjectScope !== "subject") {
|
|
437
|
-
throw new HTTPException(422, {
|
|
438
|
-
message:
|
|
439
|
-
"this capability requires a personal connection; each workspace member must connect their own account",
|
|
440
|
-
});
|
|
441
|
-
}
|
|
442
517
|
const normalized: McpServerConnectionRef = {
|
|
443
518
|
providerDomain: ref.providerDomain.trim(),
|
|
444
519
|
subjectScope,
|
|
445
520
|
...(ref.connectionId ? { connectionId: ref.connectionId } : {}),
|
|
521
|
+
...(ref.accountSelection ? { accountSelection: ref.accountSelection } : {}),
|
|
446
522
|
...(ref.authoritySource === "host" ? { authoritySource: "host" as const } : {}),
|
|
447
523
|
...(ref.provider ? { provider: ref.provider.trim() } : {}),
|
|
448
524
|
...(ref.kind ? { kind: ref.kind } : {}),
|
|
@@ -468,7 +544,7 @@ async function validateMcpCapabilityConnectionRef(
|
|
|
468
544
|
});
|
|
469
545
|
}
|
|
470
546
|
if (normalized.authoritySource === "host") {
|
|
471
|
-
|
|
547
|
+
assertNativeMcpConnectionRef(normalized);
|
|
472
548
|
return normalized;
|
|
473
549
|
}
|
|
474
550
|
|
|
@@ -480,7 +556,11 @@ async function validateMcpCapabilityConnectionRef(
|
|
|
480
556
|
input.grant.subjectId,
|
|
481
557
|
)
|
|
482
558
|
: null;
|
|
483
|
-
if (
|
|
559
|
+
if (
|
|
560
|
+
!connection &&
|
|
561
|
+
!normalized.connectionId &&
|
|
562
|
+
(subjectScope === "subject" || normalized.accountSelection === "all_eligible")
|
|
563
|
+
) {
|
|
484
564
|
const visible = await listConnectionsMetadata(
|
|
485
565
|
input.db,
|
|
486
566
|
input.workspaceId,
|
|
@@ -489,7 +569,7 @@ async function validateMcpCapabilityConnectionRef(
|
|
|
489
569
|
connection =
|
|
490
570
|
visible.find(
|
|
491
571
|
(candidate) =>
|
|
492
|
-
candidate.subjectId === input.grant.subjectId &&
|
|
572
|
+
candidate.subjectId === (subjectScope === "subject" ? input.grant.subjectId : null) &&
|
|
493
573
|
candidate.providerDomain === normalized.providerDomain &&
|
|
494
574
|
(!normalized.kind || candidate.kind === normalized.kind) &&
|
|
495
575
|
candidate.status === "active",
|
|
@@ -715,11 +795,7 @@ export async function disableCapability(input: {
|
|
|
715
795
|
message: "Remove Plugins through the Plugin Package flow",
|
|
716
796
|
});
|
|
717
797
|
}
|
|
718
|
-
|
|
719
|
-
throw new HTTPException(409, {
|
|
720
|
-
message: "Uninstall Packs through the Pack uninstall preview flow",
|
|
721
|
-
});
|
|
722
|
-
}
|
|
798
|
+
|
|
723
799
|
if (item.source === "built_in" || item.source === "configured") {
|
|
724
800
|
throw new HTTPException(409, {
|
|
725
801
|
message:
|
|
@@ -773,7 +849,7 @@ export function apiIntegrationsMatchingDelegations(
|
|
|
773
849
|
const exact = new Set(
|
|
774
850
|
delegations.map((delegation) =>
|
|
775
851
|
[
|
|
776
|
-
delegation.serverId,
|
|
852
|
+
delegation.canonicalServerId ?? delegation.serverId,
|
|
777
853
|
delegation.connectionId,
|
|
778
854
|
delegation.providerDomain.toLowerCase(),
|
|
779
855
|
delegation.kind ?? "",
|
|
@@ -1055,52 +1131,6 @@ async function requireCatalogItem(
|
|
|
1055
1131
|
return item;
|
|
1056
1132
|
}
|
|
1057
1133
|
|
|
1058
|
-
function packCatalogItem(
|
|
1059
|
-
pack: ReturnType<typeof listCapabilityPacks>[number],
|
|
1060
|
-
source: "built_in" | "manual",
|
|
1061
|
-
): CapabilityCatalogItem {
|
|
1062
|
-
const customMetadata = { ...pack.metadata };
|
|
1063
|
-
for (const key of [
|
|
1064
|
-
"packId",
|
|
1065
|
-
"version",
|
|
1066
|
-
"connectors",
|
|
1067
|
-
"knowledge",
|
|
1068
|
-
"scheduledTaskTemplates",
|
|
1069
|
-
"sandboxImage",
|
|
1070
|
-
"sandboxProviderImages",
|
|
1071
|
-
"skills",
|
|
1072
|
-
]) {
|
|
1073
|
-
delete customMetadata[key];
|
|
1074
|
-
}
|
|
1075
|
-
return CapabilityCatalogItem.parse({
|
|
1076
|
-
id: `pack:${pack.id}`,
|
|
1077
|
-
kind: "pack",
|
|
1078
|
-
source,
|
|
1079
|
-
name: pack.name,
|
|
1080
|
-
description: pack.description,
|
|
1081
|
-
category: pack.category,
|
|
1082
|
-
tags: [pack.role, pack.category, "pack"],
|
|
1083
|
-
tools: pack.tools,
|
|
1084
|
-
runtime: {
|
|
1085
|
-
available: true,
|
|
1086
|
-
notes: "Enables role-scoped tools, connectors, knowledge, and scheduled-task templates.",
|
|
1087
|
-
},
|
|
1088
|
-
metadata: {
|
|
1089
|
-
...customMetadata,
|
|
1090
|
-
packId: pack.id,
|
|
1091
|
-
version: pack.version,
|
|
1092
|
-
connectors: pack.connectors,
|
|
1093
|
-
knowledge: pack.knowledge,
|
|
1094
|
-
scheduledTaskTemplates: pack.scheduledTaskTemplates,
|
|
1095
|
-
...(pack.variableSet ? { variableSet: pack.variableSet } : {}),
|
|
1096
|
-
// Runtime composition surface only: skill names, never file content.
|
|
1097
|
-
...(pack.sandboxImage ? { sandboxImage: pack.sandboxImage } : {}),
|
|
1098
|
-
...(pack.sandboxProviderImages ? { sandboxProviderImages: pack.sandboxProviderImages } : {}),
|
|
1099
|
-
...(pack.skills.length > 0 ? { skills: pack.skills.map((skill) => skill.name) } : {}),
|
|
1100
|
-
},
|
|
1101
|
-
});
|
|
1102
|
-
}
|
|
1103
|
-
|
|
1104
1134
|
function configuredMcpCatalogItems(settings: Settings): CapabilityCatalogItem[] {
|
|
1105
1135
|
return (
|
|
1106
1136
|
settings.mcpServers
|
|
@@ -1121,6 +1151,17 @@ function configuredMcpCatalogItems(settings: Settings): CapabilityCatalogItem[]
|
|
|
1121
1151
|
category: "configured",
|
|
1122
1152
|
tags: ["mcp", ...(server.allowedTools?.length ? ["limited-tools"] : [])],
|
|
1123
1153
|
endpointUrl: server.url,
|
|
1154
|
+
authKind:
|
|
1155
|
+
server.connectionRef?.authoritySource !== "host" &&
|
|
1156
|
+
server.connectionRef?.kind === "oauth2"
|
|
1157
|
+
? "oauth2"
|
|
1158
|
+
: null,
|
|
1159
|
+
// Deployment-managed personal selectors need the same account picker
|
|
1160
|
+
// as installed connectors. Fixed bindings remain server-resolved.
|
|
1161
|
+
connectionRef:
|
|
1162
|
+
server.connectionRef?.subjectScope === "subject" && !server.connectionRef.connectionId
|
|
1163
|
+
? installationConnectionRef({ connectionRef: server.connectionRef })
|
|
1164
|
+
: null,
|
|
1124
1165
|
tools: [{ kind: "mcp", id: server.id }],
|
|
1125
1166
|
runtime: {
|
|
1126
1167
|
available: true,
|
|
@@ -1521,19 +1562,10 @@ function socialProviderConnectionCounts(item: CapabilityCatalogItem): {
|
|
|
1521
1562
|
export function applyCapabilityEnablement(
|
|
1522
1563
|
item: CapabilityCatalogItem,
|
|
1523
1564
|
installation: CapabilityInstallation | undefined,
|
|
1524
|
-
activePackIds: Set<string>,
|
|
1525
1565
|
): CapabilityCatalogItem {
|
|
1526
1566
|
if (item.kind === "skill" || item.kind === "api" || item.kind === "plugin") {
|
|
1527
1567
|
return { ...item, enabled: false, enabledReason: null, connectionRef: null };
|
|
1528
1568
|
}
|
|
1529
|
-
if (item.kind === "pack") {
|
|
1530
|
-
const enabled = activePackIds.has(packIdFromCapabilityId(item.id));
|
|
1531
|
-
return {
|
|
1532
|
-
...item,
|
|
1533
|
-
enabled,
|
|
1534
|
-
enabledReason: enabled ? "enabled" : null,
|
|
1535
|
-
};
|
|
1536
|
-
}
|
|
1537
1569
|
if (isSocialProviderIntegration(item)) {
|
|
1538
1570
|
// Provider-integration state is derived from every authoritative visible
|
|
1539
1571
|
// social Connection while the catalog is built. The catalog summary never
|
|
@@ -1557,7 +1589,7 @@ export function applyCapabilityEnablement(
|
|
|
1557
1589
|
...item,
|
|
1558
1590
|
enabled: true,
|
|
1559
1591
|
enabledReason: "managed by deployment",
|
|
1560
|
-
connectionRef:
|
|
1592
|
+
connectionRef: item.connectionRef,
|
|
1561
1593
|
};
|
|
1562
1594
|
}
|
|
1563
1595
|
if (item.source === "built_in") {
|
|
@@ -1568,11 +1600,17 @@ export function applyCapabilityEnablement(
|
|
|
1568
1600
|
}
|
|
1569
1601
|
const activeInstallation = installation?.status === "active";
|
|
1570
1602
|
const enabled = !!activeInstallation && capabilityInstallationRuntimeReady(item, installation);
|
|
1603
|
+
const connectionRef =
|
|
1604
|
+
enabled && installation ? installationConnectionRef(installation.config) : null;
|
|
1571
1605
|
return {
|
|
1572
1606
|
...item,
|
|
1607
|
+
// Older custom entries recorded the native credential kind on the
|
|
1608
|
+
// installation only. Project it without guessing from a URL or overwriting
|
|
1609
|
+
// an explicit catalog authentication contract.
|
|
1610
|
+
authKind: item.authKind ?? (connectionRef?.kind === "oauth2" ? "oauth2" : null),
|
|
1573
1611
|
enabled,
|
|
1574
1612
|
enabledReason: enabled ? "enabled" : null,
|
|
1575
|
-
connectionRef
|
|
1613
|
+
connectionRef,
|
|
1576
1614
|
};
|
|
1577
1615
|
}
|
|
1578
1616
|
|
|
@@ -1599,7 +1637,10 @@ function applyCapabilityLifecycle(item: CapabilityCatalogItem): CapabilityCatalo
|
|
|
1599
1637
|
detail: item.runtime.notes,
|
|
1600
1638
|
managedBy: item.source === "built_in" ? "platform" : null,
|
|
1601
1639
|
},
|
|
1602
|
-
actions:
|
|
1640
|
+
actions:
|
|
1641
|
+
item.enabled && item.kind === "mcp" && item.source !== "built_in"
|
|
1642
|
+
? ["disconnect", "inspect"]
|
|
1643
|
+
: ["inspect"],
|
|
1603
1644
|
};
|
|
1604
1645
|
}
|
|
1605
1646
|
|
|
@@ -1638,7 +1679,7 @@ function applyCapabilityLifecycle(item: CapabilityCatalogItem): CapabilityCatalo
|
|
|
1638
1679
|
|
|
1639
1680
|
const installed = item.enabled;
|
|
1640
1681
|
const actions: CapabilityAction[] = installed
|
|
1641
|
-
? item.kind === "
|
|
1682
|
+
? item.kind === "skill" || item.kind === "plugin"
|
|
1642
1683
|
? ["configure", "update", "uninstall", "inspect"]
|
|
1643
1684
|
: ["configure", "disconnect", "inspect"]
|
|
1644
1685
|
: item.kind === "mcp" || item.kind === "api"
|
|
@@ -1701,10 +1742,8 @@ function installationConnectionRef(
|
|
|
1701
1742
|
if (!ref || typeof ref !== "object") {
|
|
1702
1743
|
return null;
|
|
1703
1744
|
}
|
|
1704
|
-
const { authoritySource, connectionId, providerDomain, kind, subjectScope } =
|
|
1705
|
-
string,
|
|
1706
|
-
unknown
|
|
1707
|
-
>;
|
|
1745
|
+
const { authoritySource, connectionId, providerDomain, kind, subjectScope, accountSelection } =
|
|
1746
|
+
ref as Record<string, unknown>;
|
|
1708
1747
|
if (typeof providerDomain !== "string" || typeof kind !== "string") {
|
|
1709
1748
|
return null;
|
|
1710
1749
|
}
|
|
@@ -1715,10 +1754,22 @@ function installationConnectionRef(
|
|
|
1715
1754
|
// open old browser bundles cannot treat a host UUID as native OAuth state.
|
|
1716
1755
|
return null;
|
|
1717
1756
|
}
|
|
1757
|
+
if (accountSelection === "all_eligible" && connectionId === undefined) {
|
|
1758
|
+
return {
|
|
1759
|
+
providerDomain,
|
|
1760
|
+
kind,
|
|
1761
|
+
accountSelection,
|
|
1762
|
+
...(subjectScope === "subject" ? { subjectScope } : {}),
|
|
1763
|
+
};
|
|
1764
|
+
}
|
|
1718
1765
|
if (subjectScope === "subject") {
|
|
1719
1766
|
// Never project a native personal connection UUID through workspace-visible
|
|
1720
1767
|
// capability configuration, including legacy rows that still contain one.
|
|
1721
|
-
return {
|
|
1768
|
+
return {
|
|
1769
|
+
providerDomain,
|
|
1770
|
+
kind,
|
|
1771
|
+
subjectScope: "subject",
|
|
1772
|
+
};
|
|
1722
1773
|
}
|
|
1723
1774
|
if (typeof connectionId !== "string") {
|
|
1724
1775
|
return null;
|
|
@@ -1844,10 +1895,6 @@ function publicRegistryCapabilityId(name: string, version: string, endpointUrl:
|
|
|
1844
1895
|
return `mcp-registry:${slugify(name)}-${shortHash(`${name}:${version}:${endpointUrl}`)}`;
|
|
1845
1896
|
}
|
|
1846
1897
|
|
|
1847
|
-
function packIdFromCapabilityId(capabilityId: string): string {
|
|
1848
|
-
return capabilityId.replace(/^pack:/, "");
|
|
1849
|
-
}
|
|
1850
|
-
|
|
1851
1898
|
function uniqueTags(tags: string[]): string[] {
|
|
1852
1899
|
return [...new Set(tags.map((tag) => tag.trim()).filter(Boolean))];
|
|
1853
1900
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { HTTPException } from "hono/http-exception";
|
|
2
|
+
|
|
3
|
+
/** An account choice needs attention; retrying the same work cannot fix it. */
|
|
4
|
+
export class ConnectionAccountSelectionError extends HTTPException {
|
|
5
|
+
override name = "ConnectionAccountSelectionError";
|
|
6
|
+
constructor(message: string) {
|
|
7
|
+
super(422, { message });
|
|
8
|
+
}
|
|
9
|
+
}
|