@opengeni/core 0.4.1 → 0.4.3
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/LICENSE +190 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +77 -8
- package/dist/index.js.map +1 -1
- package/package.json +10 -14
- package/src/domain/capabilities.ts +95 -7
- package/src/domain/sessions.ts +2 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opengeni/core",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.3",
|
|
4
4
|
"description": "OpenGeni framework-agnostic core: the domain, access, and billing layers (neutral access, off-HTTP V2 surface). Behavior-preserving extraction from apps/api — keeps Hono's HTTPException for error throwing (typed-errors cleanup deferred).",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -32,24 +32,20 @@
|
|
|
32
32
|
},
|
|
33
33
|
"scripts": {
|
|
34
34
|
"typecheck": "tsc --noEmit",
|
|
35
|
-
"build": "tsup"
|
|
35
|
+
"build": "tsup",
|
|
36
|
+
"prepublishOnly": "bash ../../scripts/prepublish-guard"
|
|
36
37
|
},
|
|
37
38
|
"dependencies": {
|
|
38
39
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
39
40
|
"@opengeni/codex": "^0.2.1",
|
|
40
|
-
"@opengeni/config": "^0.
|
|
41
|
-
"@opengeni/contracts": "^0.
|
|
42
|
-
"@opengeni/db": "^0.
|
|
43
|
-
"@opengeni/documents": "^0.2.
|
|
44
|
-
"@opengeni/events": "^0.2.
|
|
41
|
+
"@opengeni/config": "^0.3.0",
|
|
42
|
+
"@opengeni/contracts": "^0.9.0",
|
|
43
|
+
"@opengeni/db": "^0.6.0",
|
|
44
|
+
"@opengeni/documents": "^0.2.7",
|
|
45
|
+
"@opengeni/events": "^0.2.7",
|
|
45
46
|
"@opengeni/observability": "^0.2.1",
|
|
46
|
-
"@opengeni/runtime": "^0.
|
|
47
|
-
"@opengeni/storage": "^0.2.
|
|
47
|
+
"@opengeni/runtime": "^0.4.0",
|
|
48
|
+
"@opengeni/storage": "^0.2.7",
|
|
48
49
|
"hono": "^4.12.18"
|
|
49
|
-
},
|
|
50
|
-
"devDependencies": {
|
|
51
|
-
"better-auth": "^1.6.14",
|
|
52
|
-
"tsup": "^8.5.0",
|
|
53
|
-
"typescript": "^6.0.3"
|
|
54
50
|
}
|
|
55
51
|
}
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
type CapabilityKind,
|
|
12
12
|
type CreateCapabilityCatalogItemRequest,
|
|
13
13
|
type EnableCapabilityRequest,
|
|
14
|
+
type McpServerConnectionRef,
|
|
14
15
|
} from "@opengeni/contracts";
|
|
15
16
|
import {
|
|
16
17
|
decryptEnvironmentValue,
|
|
@@ -21,6 +22,7 @@ import {
|
|
|
21
22
|
encryptEnvironmentValue,
|
|
22
23
|
getCapabilityCatalogItem,
|
|
23
24
|
getCapabilityInstallation,
|
|
25
|
+
getConnectionMetadata,
|
|
24
26
|
getPackInstallation,
|
|
25
27
|
getStoredCapabilityHeaderCiphertext,
|
|
26
28
|
getWorkspaceEnvironment,
|
|
@@ -94,7 +96,7 @@ export async function createCatalogItem(input: {
|
|
|
94
96
|
if (id.startsWith("pack:")) {
|
|
95
97
|
throw new HTTPException(422, { message: "packs are managed by OpenGeni and cannot be manually created" });
|
|
96
98
|
}
|
|
97
|
-
const source = input.payload.source === "built_in" || input.payload.source === "configured" ? "manual" : input.payload.source;
|
|
99
|
+
const source = input.payload.source === "built_in" || input.payload.source === "configured" || input.payload.source === "registry" ? "manual" : input.payload.source;
|
|
98
100
|
const metadata = {
|
|
99
101
|
...input.payload.metadata,
|
|
100
102
|
...(input.payload.kind === "mcp" && input.payload.endpointUrl && !input.payload.metadata.mcpServerId
|
|
@@ -141,13 +143,22 @@ export async function enableCapability(input: {
|
|
|
141
143
|
delete installationConfig.headers;
|
|
142
144
|
delete installationConfig.headersEncrypted;
|
|
143
145
|
delete installationConfig.headerNames;
|
|
146
|
+
delete installationConfig.connectionRef;
|
|
144
147
|
if (item.kind === "mcp") {
|
|
145
148
|
const headers = await resolveMcpCredentialHeaders(input, item);
|
|
146
|
-
|
|
149
|
+
const connectionRef = input.payload.connectionRef
|
|
150
|
+
? await validateMcpCapabilityConnectionRef(input, item, input.payload.connectionRef)
|
|
151
|
+
: null;
|
|
152
|
+
assertRequiredMcpCredentialHeaders(item, headers, connectionRef);
|
|
147
153
|
installationMetadata = {
|
|
148
154
|
...installationMetadata,
|
|
149
|
-
...
|
|
155
|
+
...(connectionRef && !headers
|
|
156
|
+
? authDeferredMcpConnectivity()
|
|
157
|
+
: await validateMcpCapabilityConnection(item, input.probeMcpServer, headers ?? undefined)),
|
|
150
158
|
};
|
|
159
|
+
if (connectionRef) {
|
|
160
|
+
installationConfig.connectionRef = connectionRef;
|
|
161
|
+
}
|
|
151
162
|
if (headers) {
|
|
152
163
|
const key = requireCapabilityHeaderEncryption(input.settings);
|
|
153
164
|
installationConfig.headersEncrypted = Object.fromEntries(
|
|
@@ -291,7 +302,67 @@ function normalizedMcpCredentialHeaders(headers: Record<string, string>): Record
|
|
|
291
302
|
return Object.fromEntries(entries);
|
|
292
303
|
}
|
|
293
304
|
|
|
294
|
-
function
|
|
305
|
+
async function validateMcpCapabilityConnectionRef(
|
|
306
|
+
input: { db: Database; grant: AccessGrant; workspaceId: string },
|
|
307
|
+
item: CapabilityCatalogItem,
|
|
308
|
+
ref: McpServerConnectionRef,
|
|
309
|
+
): Promise<McpServerConnectionRef> {
|
|
310
|
+
if (ref.subjectScope === "subject") {
|
|
311
|
+
throw new HTTPException(422, { message: "subject-owned connection refs are not supported for agent runtime use yet" });
|
|
312
|
+
}
|
|
313
|
+
const normalized: McpServerConnectionRef = {
|
|
314
|
+
providerDomain: ref.providerDomain.trim(),
|
|
315
|
+
subjectScope: "workspace",
|
|
316
|
+
...(ref.connectionId ? { connectionId: ref.connectionId } : {}),
|
|
317
|
+
...(ref.kind ? { kind: ref.kind } : {}),
|
|
318
|
+
...(ref.scopes ? { scopes: uniqueStrings(ref.scopes) } : {}),
|
|
319
|
+
...(ref.resource ? { resource: ref.resource } : {}),
|
|
320
|
+
};
|
|
321
|
+
if (!normalized.providerDomain) {
|
|
322
|
+
throw new HTTPException(422, { message: "connectionRef.providerDomain is required" });
|
|
323
|
+
}
|
|
324
|
+
if (!item.endpointUrl || !item.runtime.mcpServerId) {
|
|
325
|
+
throw new HTTPException(422, { message: "MCP capabilities need a remote streamable HTTP endpoint before they can use a connectionRef" });
|
|
326
|
+
}
|
|
327
|
+
if (!normalized.connectionId) {
|
|
328
|
+
return normalized;
|
|
329
|
+
}
|
|
330
|
+
const connection = await getConnectionMetadata(input.db, input.workspaceId, normalized.connectionId, input.grant.subjectId);
|
|
331
|
+
if (!connection) {
|
|
332
|
+
throw new HTTPException(422, { message: "connectionRef.connectionId does not reference a visible connection" });
|
|
333
|
+
}
|
|
334
|
+
if (connection.subjectId !== null) {
|
|
335
|
+
throw new HTTPException(422, { message: "agent runtime connection refs must reference workspace-shared connections in I1" });
|
|
336
|
+
}
|
|
337
|
+
if (connection.status !== "active") {
|
|
338
|
+
throw new HTTPException(422, { message: `connectionRef.connectionId is not active (${connection.status})` });
|
|
339
|
+
}
|
|
340
|
+
if (connection.providerDomain !== normalized.providerDomain) {
|
|
341
|
+
throw new HTTPException(422, { message: "connectionRef.providerDomain does not match the referenced connection" });
|
|
342
|
+
}
|
|
343
|
+
if (normalized.kind && connection.kind !== normalized.kind) {
|
|
344
|
+
throw new HTTPException(422, { message: "connectionRef.kind does not match the referenced connection" });
|
|
345
|
+
}
|
|
346
|
+
return normalized;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function authDeferredMcpConnectivity(): Record<string, unknown> {
|
|
350
|
+
return {
|
|
351
|
+
mcpConnectivity: {
|
|
352
|
+
status: "auth_deferred",
|
|
353
|
+
checkedAt: new Date().toISOString(),
|
|
354
|
+
},
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function assertRequiredMcpCredentialHeaders(
|
|
359
|
+
item: CapabilityCatalogItem,
|
|
360
|
+
headers: Record<string, string> | null,
|
|
361
|
+
connectionRef: McpServerConnectionRef | null,
|
|
362
|
+
): void {
|
|
363
|
+
if (connectionRef) {
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
295
366
|
const required = requiredCapabilityHeaders(item.metadata);
|
|
296
367
|
const names = new Set(Object.keys(headers ?? {}).map((name) => name.toLowerCase()));
|
|
297
368
|
const missing = required.filter((name) => !names.has(name.toLowerCase()));
|
|
@@ -439,7 +510,7 @@ export function settingsWithMcpCapabilityServers(settings: Settings, enabled: En
|
|
|
439
510
|
.filter((server) => !existingIds.has(server.id))
|
|
440
511
|
.flatMap((server) => {
|
|
441
512
|
const headers = decryptedCapabilityHeaders(server, encryptionKey);
|
|
442
|
-
if (headers === "unavailable") {
|
|
513
|
+
if (headers === "unavailable" && !server.connectionRef) {
|
|
443
514
|
// Without its credential headers this server can only fail auth at
|
|
444
515
|
// connect time and break agent turns; leave it out of the run.
|
|
445
516
|
return [];
|
|
@@ -451,7 +522,8 @@ export function settingsWithMcpCapabilityServers(settings: Settings, enabled: En
|
|
|
451
522
|
...(server.allowedTools ? { allowedTools: server.allowedTools } : {}),
|
|
452
523
|
...(server.timeoutMs ? { timeoutMs: server.timeoutMs } : {}),
|
|
453
524
|
cacheToolsList: server.cacheToolsList ?? false,
|
|
454
|
-
...(headers ? { headers } : {}),
|
|
525
|
+
...(headers && headers !== "unavailable" ? { headers } : {}),
|
|
526
|
+
...(server.connectionRef ? { connectionRef: server.connectionRef } : {}),
|
|
455
527
|
}];
|
|
456
528
|
});
|
|
457
529
|
return dynamicServers.length ? { ...settings, mcpServers: [...settings.mcpServers, ...dynamicServers] } : settings;
|
|
@@ -786,6 +858,10 @@ function uniqueTags(tags: string[]): string[] {
|
|
|
786
858
|
return [...new Set(tags.map((tag) => tag.trim()).filter(Boolean))];
|
|
787
859
|
}
|
|
788
860
|
|
|
861
|
+
function uniqueStrings(values: string[]): string[] {
|
|
862
|
+
return [...new Set(values.map((value) => value.trim()).filter(Boolean))];
|
|
863
|
+
}
|
|
864
|
+
|
|
789
865
|
function slugify(value: string): string {
|
|
790
866
|
return value.toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 60) || "capability";
|
|
791
867
|
}
|
|
@@ -938,7 +1014,10 @@ function capabilityInstallationRuntimeReady(
|
|
|
938
1014
|
return false;
|
|
939
1015
|
}
|
|
940
1016
|
const connectivity = installation.metadata.mcpConnectivity;
|
|
941
|
-
return !!connectivity
|
|
1017
|
+
return !!connectivity
|
|
1018
|
+
&& typeof connectivity === "object"
|
|
1019
|
+
&& "status" in connectivity
|
|
1020
|
+
&& (connectivity.status === "ok" || connectivity.status === "auth_deferred");
|
|
942
1021
|
}
|
|
943
1022
|
|
|
944
1023
|
/**
|
|
@@ -946,6 +1025,9 @@ function capabilityInstallationRuntimeReady(
|
|
|
946
1025
|
* capability's declared credential requirements.
|
|
947
1026
|
*/
|
|
948
1027
|
function storedCredentialHeadersSatisfy(item: CapabilityCatalogItem, installation: CapabilityInstallation): boolean {
|
|
1028
|
+
if (storedConnectionRef(installation.config)) {
|
|
1029
|
+
return true;
|
|
1030
|
+
}
|
|
949
1031
|
const storedNames = new Set(
|
|
950
1032
|
(Array.isArray(installation.config.headerNames) ? installation.config.headerNames : [])
|
|
951
1033
|
.filter((name): name is string => typeof name === "string")
|
|
@@ -957,3 +1039,9 @@ function storedCredentialHeadersSatisfy(item: CapabilityCatalogItem, installatio
|
|
|
957
1039
|
}
|
|
958
1040
|
return !item.authModel || storedNames.size > 0;
|
|
959
1041
|
}
|
|
1042
|
+
|
|
1043
|
+
function storedConnectionRef(config: Record<string, unknown>): boolean {
|
|
1044
|
+
const ref = config.connectionRef;
|
|
1045
|
+
return !!ref && typeof ref === "object" && !Array.isArray(ref)
|
|
1046
|
+
&& typeof (ref as { providerDomain?: unknown }).providerDomain === "string";
|
|
1047
|
+
}
|
package/src/domain/sessions.ts
CHANGED
|
@@ -105,6 +105,7 @@ function mcpServerConfigFromInput(server: SessionMcpServerInput): Settings["mcpS
|
|
|
105
105
|
...(server.allowedTools ? { allowedTools: server.allowedTools } : {}),
|
|
106
106
|
...(server.timeoutMs ? { timeoutMs: server.timeoutMs } : {}),
|
|
107
107
|
cacheToolsList: server.cacheToolsList ?? false,
|
|
108
|
+
...(server.requireApproval !== undefined ? { requireApproval: server.requireApproval } : {}),
|
|
108
109
|
};
|
|
109
110
|
}
|
|
110
111
|
|
|
@@ -170,6 +171,7 @@ function validateSessionMcpServersForCreate(
|
|
|
170
171
|
allowedTools: server.allowedTools ?? null,
|
|
171
172
|
timeoutMs: server.timeoutMs ?? null,
|
|
172
173
|
cacheToolsList: server.cacheToolsList ?? false,
|
|
174
|
+
requireApproval: server.requireApproval ?? null,
|
|
173
175
|
headersEncrypted,
|
|
174
176
|
});
|
|
175
177
|
metadata.push({
|