@opengeni/core 3.0.0-canary.0 → 3.0.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.
@@ -209,6 +209,26 @@ const maxSessionMcpCredentialHeaderValueLength = 4096;
209
209
  // the generic lossy projection cannot silently rewrite this audit fact.
210
210
  const maxToolPolicyAuditRefs = 40;
211
211
 
212
+ function withoutExcludedMcpServers(
213
+ tools: ToolRef[],
214
+ excludedIds: readonly string[] = [],
215
+ ): ToolRef[] {
216
+ const excluded = new Set(excludedIds);
217
+ return tools.filter((tool) => tool.id === "opengeni" || !excluded.has(tool.id));
218
+ }
219
+
220
+ function defaultPolicyExclusions(
221
+ ids: readonly string[] = [],
222
+ ): Pick<SessionToolPolicy, "excludedMcpServerIds"> {
223
+ const sorted = [...new Set(ids)].filter((id) => id !== "opengeni").sort();
224
+ if (sorted.length > 64 || sorted.some((id) => id.length > 200 || !/^[A-Za-z0-9_-]+$/.test(id))) {
225
+ throw new HTTPException(422, {
226
+ message: "connector exclusions must contain at most 64 valid MCP server IDs",
227
+ });
228
+ }
229
+ return sorted.length ? { excludedMcpServerIds: sorted } : {};
230
+ }
231
+
212
232
  function isCatalogOverlayModel(modelId: string | null | undefined): boolean {
213
233
  return (
214
234
  modelId?.startsWith(WORKSPACE_GATEWAY_MODEL_ID_PREFIX) === true ||
@@ -2395,6 +2415,11 @@ async function createSessionForRequestInFileScope(
2395
2415
  });
2396
2416
  }
2397
2417
  const toolsProvided = hasOwnProperty(rawPayload, "tools");
2418
+ if (toolsProvided && payload.excludedMcpServerIds !== undefined) {
2419
+ throw new HTTPException(422, {
2420
+ message: "connector exclusions require workspace-default tools",
2421
+ });
2422
+ }
2398
2423
  // Visibility became durable draft state after older clients had already
2399
2424
  // written rows without it. Compare it only when the create request supplied
2400
2425
  // the field explicitly; the parsed schema default must not manufacture a
@@ -2417,6 +2442,9 @@ async function createSessionForRequestInFileScope(
2417
2442
  )
2418
2443
  : parentSession.tools,
2419
2444
  runtimeSettings,
2445
+ ).filter(
2446
+ (tool) =>
2447
+ tool.id === "opengeni" || !parentSession.toolPolicy.excludedMcpServerIds?.includes(tool.id),
2420
2448
  );
2421
2449
  if (toolsProvided) {
2422
2450
  assertToolRefsSubset(
@@ -2434,6 +2462,7 @@ async function createSessionForRequestInFileScope(
2434
2462
  toolPolicy = {
2435
2463
  mode: parentTracksWorkspaceDefaults ? "workspace_default" : "inherited",
2436
2464
  inheritedFromSessionId: parentSession.id,
2465
+ ...defaultPolicyExclusions(parentSession.toolPolicy.excludedMcpServerIds),
2437
2466
  };
2438
2467
  }
2439
2468
  } else if (toolsProvided) {
@@ -2448,6 +2477,21 @@ async function createSessionForRequestInFileScope(
2448
2477
  );
2449
2478
  toolPolicy = { mode: "workspace_default", inheritedFromSessionId: null };
2450
2479
  }
2480
+ if (payload.excludedMcpServerIds !== undefined) {
2481
+ if (toolPolicy.mode !== "workspace_default") {
2482
+ throw new HTTPException(403, {
2483
+ message: "connector exclusions require workspace-default tools",
2484
+ });
2485
+ }
2486
+ toolPolicy = {
2487
+ ...toolPolicy,
2488
+ ...defaultPolicyExclusions([
2489
+ ...(toolPolicy.excludedMcpServerIds ?? []),
2490
+ ...payload.excludedMcpServerIds,
2491
+ ]),
2492
+ };
2493
+ }
2494
+ selectedTools = withoutExcludedMcpServers(selectedTools, toolPolicy.excludedMcpServerIds);
2451
2495
  // The first-party MCP server is attached to EVERY session. Registration is
2452
2496
  // independently intersected with the exact model-visible selection and the
2453
2497
  // tool's permission/target authorization predicate, so attachment alone
@@ -2584,6 +2628,9 @@ async function createSessionForRequestInFileScope(
2584
2628
  reasoningEffort,
2585
2629
  latencyMode,
2586
2630
  options: {
2631
+ ...(payload.excludedMcpServerIds !== undefined
2632
+ ? { excludedMcpServerIds: payload.excludedMcpServerIds }
2633
+ : {}),
2587
2634
  ...(visibilityProvided ? { visibility: payload.visibility } : {}),
2588
2635
  ...(payload.sandboxBackend ? { sandboxBackend: payload.sandboxBackend } : {}),
2589
2636
  ...(payload.targetSandboxId ? { targetSandboxId: payload.targetSandboxId } : {}),
@@ -3976,6 +4023,12 @@ function toolPolicyAuditSnapshot(
3976
4023
  return {
3977
4024
  mode: policy.mode,
3978
4025
  inheritedFromSessionId: policy.inheritedFromSessionId,
4026
+ ...(policy.excludedMcpServerIds?.length
4027
+ ? {
4028
+ excludedMcpServerIds: policy.excludedMcpServerIds.slice(0, maxToolPolicyAuditRefs),
4029
+ excludedMcpServerCount: policy.excludedMcpServerIds.length,
4030
+ }
4031
+ : {}),
3979
4032
  // IDs only: no MCP URLs, names, headers, credentials, schemas, or args.
3980
4033
  toolIds: [...toolRefs]
3981
4034
  .sort((left, right) => `${left.kind}:${left.id}`.localeCompare(`${right.kind}:${right.id}`))
@@ -3984,7 +4037,9 @@ function toolPolicyAuditSnapshot(
3984
4037
  toolCount: allToolRefs.length,
3985
4038
  firstPartyMcpTools: [...firstPartyMcpTools].sort(),
3986
4039
  firstPartyMcpToolCount: firstPartyMcpTools.length,
3987
- truncated: allToolRefs.length > toolRefs.length,
4040
+ truncated:
4041
+ allToolRefs.length > toolRefs.length ||
4042
+ (policy.excludedMcpServerIds?.length ?? 0) > maxToolPolicyAuditRefs,
3988
4043
  };
3989
4044
  }
3990
4045
 
@@ -4028,9 +4083,31 @@ export async function updateSessionToolPolicy(
4028
4083
  );
4029
4084
  const explicitRequest = request.mode === "workspace_default" ? null : request;
4030
4085
  const requestedMode = explicitRequest ? "explicit" : "workspace_default";
4086
+ const connectorOnlyEdit =
4087
+ request.mode === "workspace_default" && request.excludedMcpServerIds !== undefined;
4088
+ const requestedExclusions =
4089
+ request.mode === "workspace_default" ? (request.excludedMcpServerIds ?? []) : [];
4031
4090
  const explicitRequestedTools = explicitRequest
4032
4091
  ? (() => {
4033
- const validatedTools = validateToolRefs(explicitRequest.tools, runtimeSettings);
4092
+ // A disconnected stored selection remains policy truth, not executable
4093
+ // authority. Preserve exact existing refs while validating every new or
4094
+ // changed ref against the current runtime registry.
4095
+ const availableIds = new Set(runtimeSettings.mcpServers.map((server) => server.id));
4096
+ const retainedUnavailableRefs = explicitRequest.tools.filter(
4097
+ (tool) =>
4098
+ !availableIds.has(tool.id) &&
4099
+ existingSession.tools.some((existing) => stableJson(existing) === stableJson(tool)),
4100
+ );
4101
+ const retainedIds = new Set(retainedUnavailableRefs.map((tool) => tool.id));
4102
+ const validatedCurrentRefs = validateToolRefs(
4103
+ explicitRequest.tools.filter((tool) => !retainedIds.has(tool.id)),
4104
+ runtimeSettings,
4105
+ );
4106
+ const validatedTools = explicitRequest.tools.filter(
4107
+ (tool) =>
4108
+ retainedUnavailableRefs.includes(tool) ||
4109
+ validatedCurrentRefs.some((validated) => validated.id === tool.id),
4110
+ );
4034
4111
  const validatedIds = new Set(validatedTools.map((tool) => `${tool.kind}:${tool.id}`));
4035
4112
  const unknown = explicitRequest.tools.find(
4036
4113
  (tool) => !validatedIds.has(`${tool.kind}:${tool.id}`),
@@ -4100,6 +4177,9 @@ export async function updateSessionToolPolicy(
4100
4177
  )
4101
4178
  : parent.tools,
4102
4179
  runtimeSettings,
4180
+ ).filter(
4181
+ (tool) =>
4182
+ tool.id === "opengeni" || !parent.toolPolicy.excludedMcpServerIds?.includes(tool.id),
4103
4183
  );
4104
4184
  const deploymentAllowedFirstPartyMcpTools = new Set(
4105
4185
  deploymentFirstPartyMcpToolPolicy.allowed,
@@ -4119,6 +4199,10 @@ export async function updateSessionToolPolicy(
4119
4199
  nextPolicy = {
4120
4200
  mode: "workspace_default",
4121
4201
  inheritedFromSessionId: parent.id,
4202
+ ...defaultPolicyExclusions([
4203
+ ...(parent.toolPolicy.excludedMcpServerIds ?? []),
4204
+ ...requestedExclusions,
4205
+ ]),
4122
4206
  };
4123
4207
  } else {
4124
4208
  nextTools = explicitRequestedTools!;
@@ -4149,41 +4233,88 @@ export async function updateSessionToolPolicy(
4149
4233
  requestedMode === "workspace_default"
4150
4234
  ? workspaceDefaultFirstPartyTools
4151
4235
  : explicitRequestedFirstPartyTools!;
4152
- nextPolicy = { mode: requestedMode, inheritedFromSessionId: null };
4153
- if (agentAttemptCaller) {
4154
- // A human or API key may widen a top-level session; a live agent
4155
- // attempt may only narrow relative to the session's CURRENT
4156
- // effective policy, in either mode. Adopting workspace defaults is a
4157
- // widen whenever it adds a server or tool the session does not hold.
4158
- const sessionTracksWorkspaceDefaults = session.toolPolicy?.mode === "workspace_default";
4159
- const currentEffectiveTools = withFirstPartyTools(
4160
- sessionTracksWorkspaceDefaults
4161
- ? withWorkspaceDefaultMcpTools(
4162
- availableToolRefs(session.tools, runtimeSettings),
4236
+ nextPolicy = {
4237
+ mode: requestedMode,
4238
+ inheritedFromSessionId: null,
4239
+ ...(requestedMode === "workspace_default"
4240
+ ? defaultPolicyExclusions(requestedExclusions)
4241
+ : {}),
4242
+ };
4243
+ }
4244
+ if (connectorOnlyEdit) {
4245
+ if (session.toolPolicy.mode !== "workspace_default") {
4246
+ throw new HTTPException(409, {
4247
+ message: "adopt workspace defaults before editing connector exclusions",
4248
+ });
4249
+ }
4250
+ // A connector switch never rewrites built-in tool choices or selected refs.
4251
+ nextTools = session.tools;
4252
+ nextFirstPartyMcpTools = [
4253
+ ...(session.firstPartyMcpTools ?? deploymentFirstPartyMcpToolPolicy.default),
4254
+ ];
4255
+ }
4256
+ if (!connectorOnlyEdit) {
4257
+ nextTools = withoutExcludedMcpServers(nextTools, nextPolicy.excludedMcpServerIds);
4258
+ }
4259
+ if (agentAttemptCaller && !session.parentSessionId) {
4260
+ // A human or API key may widen a top-level session; a live agent
4261
+ // attempt may only narrow relative to the session's CURRENT
4262
+ // effective policy, in either mode. Adopting workspace defaults is a
4263
+ // widen whenever it adds a server or tool the session does not hold.
4264
+ const sessionTracksWorkspaceDefaults = session.toolPolicy?.mode === "workspace_default";
4265
+ const currentEffectiveTools = withFirstPartyTools(
4266
+ sessionTracksWorkspaceDefaults
4267
+ ? withWorkspaceDefaultMcpTools(
4268
+ availableToolRefs(session.tools, runtimeSettings),
4269
+ deps.settings,
4270
+ runtimeSettings,
4271
+ workspaceSessionToolDefaults,
4272
+ )
4273
+ : session.tools,
4274
+ runtimeSettings,
4275
+ );
4276
+ const currentAllowedTools = withoutExcludedMcpServers(
4277
+ currentEffectiveTools,
4278
+ session.toolPolicy.excludedMcpServerIds,
4279
+ );
4280
+ const nextEffectiveTools =
4281
+ nextPolicy.mode === "workspace_default"
4282
+ ? withFirstPartyTools(
4283
+ withWorkspaceDefaultMcpTools(
4284
+ availableToolRefs(nextTools, runtimeSettings),
4163
4285
  deps.settings,
4164
4286
  runtimeSettings,
4165
4287
  workspaceSessionToolDefaults,
4166
- )
4167
- : session.tools,
4168
- runtimeSettings,
4169
- );
4170
- assertToolRefsSubset(
4171
- nextTools,
4172
- currentEffectiveTools,
4173
- "an agent may only narrow its session tool policy",
4174
- );
4175
- const currentFirstPartyCeiling = effectiveFirstPartyMcpToolCeiling(
4176
- session.firstPartyMcpTools,
4177
- deploymentFirstPartyMcpToolPolicy,
4178
- );
4179
- const widenedFirstPartyTool = nextFirstPartyMcpTools.find(
4180
- (tool) => !currentFirstPartyCeiling.has(tool),
4181
- );
4182
- if (widenedFirstPartyTool) {
4183
- throw new HTTPException(403, {
4184
- message: `an agent may only narrow its session OpenGeni tools: ${widenedFirstPartyTool}`,
4185
- });
4186
- }
4288
+ ),
4289
+ runtimeSettings,
4290
+ )
4291
+ : nextTools;
4292
+ if (
4293
+ nextPolicy.mode === "workspace_default" &&
4294
+ (session.toolPolicy.excludedMcpServerIds ?? []).some(
4295
+ (id) => !nextPolicy.excludedMcpServerIds?.includes(id),
4296
+ )
4297
+ ) {
4298
+ throw new HTTPException(403, {
4299
+ message: "an agent may not remove session connector exclusions",
4300
+ });
4301
+ }
4302
+ assertToolRefsSubset(
4303
+ withoutExcludedMcpServers(nextEffectiveTools, nextPolicy.excludedMcpServerIds),
4304
+ currentAllowedTools,
4305
+ "an agent may only narrow its session tool policy",
4306
+ );
4307
+ const currentFirstPartyCeiling = effectiveFirstPartyMcpToolCeiling(
4308
+ session.firstPartyMcpTools,
4309
+ deploymentFirstPartyMcpToolPolicy,
4310
+ );
4311
+ const widenedFirstPartyTool = nextFirstPartyMcpTools.find(
4312
+ (tool) => !currentFirstPartyCeiling.has(tool),
4313
+ );
4314
+ if (widenedFirstPartyTool) {
4315
+ throw new HTTPException(403, {
4316
+ message: `an agent may only narrow its session OpenGeni tools: ${widenedFirstPartyTool}`,
4317
+ });
4187
4318
  }
4188
4319
  }
4189
4320
 
package/src/index.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from "./domain/skills";
2
+ export * from "./domain/organization-integration-catalog";
2
3
  export * from "./domain/knowledge";
3
4
  export * from "./domain/knowledge-search";
4
5
  // @opengeni/core — the framework-agnostic OpenGeni core.
@@ -129,5 +130,8 @@ export { withSiteSessionOrigin } from "./site-session-origin";
129
130
  export { fileOwnerContextForAccess, fileOwnerContextForAgent } from "./domain/file-owner";
130
131
 
131
132
  export { prepareKnowledgeFile } from "./domain/knowledge-files";
133
+ export { prepareKnowledgeSave } from "./domain/knowledge-preparation";
132
134
 
133
135
  export { retainKnowledgeMessage } from "./domain/knowledge-messages";
136
+
137
+ export * from "./domain/connector-tool-permissions";