@opengeni/core 0.2.1 → 0.3.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/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { Settings } from '@opengeni/config';
2
- import { ScheduledTask, Document, Permission, AccessContext, AccessGrant, LimitAction, LimitDecision, CapabilityCatalogResponse, CreateCapabilityCatalogItemRequest, CapabilityCatalogItem, CapabilityInstallation, EnableCapabilityRequest, WorkspaceEnvironment, CapabilityPack, SocialConnection, ScheduledTaskAgentConfig, ToolRef, ResourceRef, CreateScheduledTaskRequest, UpdateScheduledTaskRequest, ReasoningEffort, SessionEvent, SessionTurn, GoalSpec, Session, WorkspaceMember } from '@opengeni/contracts';
2
+ import { ScheduledTask, Document, Permission, AccessContext, AccessGrant, LimitAction, LimitDecision, CapabilityCatalogResponse, CreateCapabilityCatalogItemRequest, CapabilityCatalogItem, CapabilityInstallation, EnableCapabilityRequest, WorkspaceEnvironment, CapabilityPack, SocialConnection, ScheduledTaskAgentConfig, ToolRef, ResourceRef, CreateScheduledTaskRequest, UpdateScheduledTaskRequest, ReasoningEffort, SessionMcpCredentialUpdateInput, SessionEvent, SessionTurn, GoalSpec, SessionMcpServerMetadata, Session, WorkspaceMember } from '@opengeni/contracts';
3
3
  export { mergeToolRefs, stableJson } from '@opengeni/contracts';
4
- import { Database, SandboxRecord, EnabledMcpCapabilityServer, UpdateScheduledTaskInput } from '@opengeni/db';
4
+ import { Database, SandboxRecord, EnabledMcpCapabilityServer, UpdateScheduledTaskInput, CreateSessionMcpServerInput, UpdateSessionMcpServerCredentialsInput } from '@opengeni/db';
5
5
  import { DocumentServices } from '@opengeni/documents';
6
6
  import { EventBus } from '@opengeni/events';
7
7
  import { Observability } from '@opengeni/observability';
@@ -534,6 +534,7 @@ declare function manualScheduledTaskTriggerWorkflowId(taskId: string, triggerTok
534
534
  */
535
535
  declare function manualScheduledTaskTriggerUsageKey(workspaceId: string, taskId: string, triggerToken: string): string;
536
536
 
537
+ declare function settingsWithSessionMcpServerMetadata(settings: Settings, servers: SessionMcpServerMetadata[]): Settings;
537
538
  declare function createAndStartSession(input: {
538
539
  db: Database;
539
540
  bus: EventBus;
@@ -554,6 +555,8 @@ declare function createAndStartSession(input: {
554
555
  } | null;
555
556
  goal?: GoalSpec | null;
556
557
  firstPartyMcpPermissions?: Permission[] | null;
558
+ mcpServers?: CreateSessionMcpServerInput[];
559
+ sessionMcpServers?: SessionMcpServerMetadata[];
557
560
  parentSessionId?: string | null;
558
561
  createIdempotencyKey?: string | null;
559
562
  sandboxGroupId?: string | null;
@@ -597,7 +600,14 @@ declare function createAndStartSession(input: {
597
600
  activeSandboxId: string | null;
598
601
  activeEpoch: number;
599
602
  environmentId: string | null;
600
- firstPartyMcpPermissions: ("account:read" | "account:admin" | "members:manage" | "workspace:create" | "billing:read" | "billing:manage" | "workspace:read" | "workspace:admin" | "sessions:create" | "sessions:read" | "sessions:control" | "stream:view" | "stream:control" | "stream:acknowledge" | "files:upload" | "files:read" | "files:write" | "terminal:attach" | "documents:manage" | "documents:search" | "scheduled_tasks:manage" | "scheduled_tasks:run" | "github:manage" | "github:use" | "api_keys:manage" | "environments:manage" | "environments:use" | "goals:manage" | "enrollments:read" | "enrollments:manage")[] | null;
603
+ firstPartyMcpPermissions: ("account:read" | "account:admin" | "members:manage" | "workspace:create" | "billing:read" | "billing:manage" | "workspace:read" | "workspace:admin" | "sessions:create" | "sessions:read" | "sessions:control" | "stream:view" | "stream:control" | "stream:acknowledge" | "files:upload" | "files:read" | "files:write" | "terminal:attach" | "documents:manage" | "documents:search" | "scheduled_tasks:manage" | "scheduled_tasks:run" | "github:manage" | "github:use" | "api_keys:manage" | "environments:manage" | "environments:use" | "mcp_servers:attach" | "goals:manage" | "enrollments:read" | "enrollments:manage")[] | null;
604
+ mcpServers: {
605
+ id: string;
606
+ name: string | null;
607
+ url: string;
608
+ headerNames: string[];
609
+ credentialVersion: number;
610
+ }[];
601
611
  parentSessionId: string | null;
602
612
  createIdempotencyKey: string | null;
603
613
  temporalWorkflowId: string | null;
@@ -650,6 +660,7 @@ declare function postUserMessageTurn(input: {
650
660
  model?: string | null;
651
661
  reasoningEffort?: Settings["openaiReasoningEffort"] | null;
652
662
  clientEventId?: string;
663
+ mcpCredentialUpdates?: UpdateSessionMcpServerCredentialsInput[];
653
664
  }): Promise<{
654
665
  accepted: SessionEvent;
655
666
  turn: SessionTurn;
@@ -677,6 +688,7 @@ declare function acceptSessionUserMessage(deps: ApiRouteDeps, grant: AccessGrant
677
688
  model?: string | null;
678
689
  reasoningEffort?: ReasoningEffort | null;
679
690
  clientEventId?: string;
691
+ mcpCredentialUpdates?: SessionMcpCredentialUpdateInput[];
680
692
  }): Promise<{
681
693
  accepted: SessionEvent;
682
694
  turn: SessionTurn;
@@ -732,4 +744,4 @@ declare function assertWorkspaceDeletable(input: {
732
744
  activeSessionCount: number;
733
745
  }): void;
734
746
 
735
- export { type AccessDeps, type ApiRouteDeps, type ApiSandboxClient, type ApiSandboxSession, type AppDependencies, type ChannelARoutingServices, type DocumentIndexClient, type FleetContext, type FleetListResult, type FleetLiveness, type FleetSandboxEntry, type FleetServices, type FleetSwapResult, type LimitCheckInput, MARKETING_SOCIAL_PACK_ID, MAX_ENVIRONMENTS_PER_WORKSPACE, MAX_VARIABLES_PER_ENVIRONMENT, type ManagedAuth, type McpCapabilityProbe, type McpCapabilityProbeInput, type McpCapabilityProbeResult, type ObjectStorageDependency, type ProvisionResult, type ResumeBoxByIdInput, type ResumedSandboxSession, type RunOnOp, type RunOnResult, type SessionWorkflowClient, acceptSessionUserMessage, assertAllowedEnvironmentVariableName, assertConfiguredModel, assertPackSandboxImageCompatible, assertWorkspaceDeletable, assertWorkspaceMemberRemovable, buildCapabilityCatalog, buildFleetContextForSession, buildMarketingDailyAnalysisAgentConfig, checkLimit, createAndStartSession, createCatalogItem, createSessionForRequest, createValidatedScheduledTask, disableCapability, discoverMcpRegistryCapabilities, enableCapability, enabledCapabilityMcpToolRefs, getCapabilityPack, hasPermission, isBuiltInCapabilityPack, isUserMember, listCapabilityPacks, listFleet, listWorkspaceCapabilityPacks, manualScheduledTaskTriggerUsageKey, manualScheduledTaskTriggerWorkflowId, memberCanAdminister, mergeResourceRefs, normalizeResources, officialMcpRegistryUrl, postUserMessageTurn, provisionSandbox, reasoningEffortForSession, recordEnvironmentAuditEvent, recordWorkspaceUsage, relayConfigFromSettings, relayDialBaseFromSettings, requireAccessContext, requireAccessGrant, requireEnvironmentEncryption, requireEnvironmentForApi, requireLimit, requirePermission, requireQueuedTurnForApi, requireScheduledTaskForApi, resolveCapabilityPack, resolveMemberSubjectId, restoreScheduledTask, routingEnabled, runOnSandbox, scheduledTaskTemporalScheduleId, scheduledTaskToolsProvided, scheduledTaskTriggerToken, settingsWithEnabledCapabilityMcpServers, settingsWithMcpCapabilityServers, swapActiveSandbox, syncCreatedScheduledTask, syncUpdatedScheduledTask, updateSessionTitle, validateEnvironmentAttachment, validateFileResources, validateGitHubRepositorySelection, validateGitHubRepositorySelectionShape, validateMcpCapabilityConnection, validateToolRefs, validatedScheduledTaskUpdate, withDefaultEnabledCapabilityMcpTools, workflowIdForSession, wrapChannelABoxWithRouting };
747
+ export { type AccessDeps, type ApiRouteDeps, type ApiSandboxClient, type ApiSandboxSession, type AppDependencies, type ChannelARoutingServices, type DocumentIndexClient, type FleetContext, type FleetListResult, type FleetLiveness, type FleetSandboxEntry, type FleetServices, type FleetSwapResult, type LimitCheckInput, MARKETING_SOCIAL_PACK_ID, MAX_ENVIRONMENTS_PER_WORKSPACE, MAX_VARIABLES_PER_ENVIRONMENT, type ManagedAuth, type McpCapabilityProbe, type McpCapabilityProbeInput, type McpCapabilityProbeResult, type ObjectStorageDependency, type ProvisionResult, type ResumeBoxByIdInput, type ResumedSandboxSession, type RunOnOp, type RunOnResult, type SessionWorkflowClient, acceptSessionUserMessage, assertAllowedEnvironmentVariableName, assertConfiguredModel, assertPackSandboxImageCompatible, assertWorkspaceDeletable, assertWorkspaceMemberRemovable, buildCapabilityCatalog, buildFleetContextForSession, buildMarketingDailyAnalysisAgentConfig, checkLimit, createAndStartSession, createCatalogItem, createSessionForRequest, createValidatedScheduledTask, disableCapability, discoverMcpRegistryCapabilities, enableCapability, enabledCapabilityMcpToolRefs, getCapabilityPack, hasPermission, isBuiltInCapabilityPack, isUserMember, listCapabilityPacks, listFleet, listWorkspaceCapabilityPacks, manualScheduledTaskTriggerUsageKey, manualScheduledTaskTriggerWorkflowId, memberCanAdminister, mergeResourceRefs, normalizeResources, officialMcpRegistryUrl, postUserMessageTurn, provisionSandbox, reasoningEffortForSession, recordEnvironmentAuditEvent, recordWorkspaceUsage, relayConfigFromSettings, relayDialBaseFromSettings, requireAccessContext, requireAccessGrant, requireEnvironmentEncryption, requireEnvironmentForApi, requireLimit, requirePermission, requireQueuedTurnForApi, requireScheduledTaskForApi, resolveCapabilityPack, resolveMemberSubjectId, restoreScheduledTask, routingEnabled, runOnSandbox, scheduledTaskTemporalScheduleId, scheduledTaskToolsProvided, scheduledTaskTriggerToken, settingsWithEnabledCapabilityMcpServers, settingsWithMcpCapabilityServers, settingsWithSessionMcpServerMetadata, swapActiveSandbox, syncCreatedScheduledTask, syncUpdatedScheduledTask, updateSessionTitle, validateEnvironmentAttachment, validateFileResources, validateGitHubRepositorySelection, validateGitHubRepositorySelectionShape, validateMcpCapabilityConnection, validateToolRefs, validatedScheduledTaskUpdate, withDefaultEnabledCapabilityMcpTools, workflowIdForSession, wrapChannelABoxWithRouting };
package/dist/index.js CHANGED
@@ -1664,22 +1664,21 @@ import {
1664
1664
  import { HTTPException as HTTPException7 } from "hono/http-exception";
1665
1665
  function validateToolRefs(tools, settings) {
1666
1666
  const mcpServerIds = new Set(settings.mcpServers.map((server) => server.id));
1667
- const selected = /* @__PURE__ */ new Set();
1668
1667
  const out = [];
1669
1668
  for (const tool of tools) {
1670
1669
  if (tool.kind !== "mcp") {
1671
1670
  throw new HTTPException7(422, { message: `unsupported tool kind: ${tool.kind}` });
1672
1671
  }
1672
+ const optional = tool.optional === true;
1673
1673
  if (!mcpServerIds.has(tool.id)) {
1674
+ if (optional) {
1675
+ continue;
1676
+ }
1674
1677
  throw new HTTPException7(422, { message: `unknown MCP server id: ${tool.id}` });
1675
1678
  }
1676
- if (selected.has(tool.id)) {
1677
- continue;
1678
- }
1679
- selected.add(tool.id);
1680
- out.push({ kind: "mcp", id: tool.id });
1679
+ out.push(optional ? { kind: "mcp", id: tool.id, optional: true } : { kind: "mcp", id: tool.id });
1681
1680
  }
1682
- return out;
1681
+ return mergeToolRefs([], out);
1683
1682
  }
1684
1683
  function enabledCapabilityMcpToolRefs(settings, runtimeSettings) {
1685
1684
  const configuredIds = new Set(settings.mcpServers.map((server) => server.id));
@@ -1865,6 +1864,7 @@ import {
1865
1864
  createSessionGoal,
1866
1865
  createSessionWithIdempotencyKey,
1867
1866
  enqueueSessionTurn,
1867
+ encryptEnvironmentValue as encryptEnvironmentValue2,
1868
1868
  getAnySessionInGroup,
1869
1869
  getEnrollment as getEnrollment2,
1870
1870
  listDistinctEnvironmentIdsInGroup,
@@ -1878,6 +1878,140 @@ import {
1878
1878
  } from "@opengeni/db";
1879
1879
  import { appendAndPublishEvents } from "@opengeni/events";
1880
1880
  import { HTTPException as HTTPException8 } from "hono/http-exception";
1881
+ var reservedSessionMcpServerIds = /* @__PURE__ */ new Set(["opengeni", "files", "docs", "codex_apps"]);
1882
+ var maxSessionMcpCredentialHeaders = 16;
1883
+ var maxSessionMcpCredentialHeaderValueLength = 4096;
1884
+ var sessionMcpCredentialHeaderName = /^[A-Za-z0-9!#$%&'*+.^_`|~-]+$/;
1885
+ function normalizedSessionMcpCredentialHeaders(headers) {
1886
+ if (!headers) {
1887
+ return {};
1888
+ }
1889
+ const entries = Object.entries(headers).map(([name, value]) => [name.trim(), value]).filter(([name]) => name.length > 0);
1890
+ if (entries.length > maxSessionMcpCredentialHeaders) {
1891
+ throw new HTTPException8(422, { message: `a session MCP server supports at most ${maxSessionMcpCredentialHeaders} credential headers` });
1892
+ }
1893
+ const seen = /* @__PURE__ */ new Set();
1894
+ for (const [name, value] of entries) {
1895
+ if (!sessionMcpCredentialHeaderName.test(name)) {
1896
+ throw new HTTPException8(422, { message: `invalid credential header name: ${name}` });
1897
+ }
1898
+ const lower = name.toLowerCase();
1899
+ if (seen.has(lower)) {
1900
+ throw new HTTPException8(422, { message: `duplicate credential header name: ${name}` });
1901
+ }
1902
+ seen.add(lower);
1903
+ if (value.length === 0 || value.length > maxSessionMcpCredentialHeaderValueLength) {
1904
+ throw new HTTPException8(422, { message: `credential header ${name} must be 1-${maxSessionMcpCredentialHeaderValueLength} characters` });
1905
+ }
1906
+ if (/[\u0000-\u0008\u000A-\u001F\u007F]/.test(value)) {
1907
+ throw new HTTPException8(422, { message: `credential header ${name} contains forbidden control characters` });
1908
+ }
1909
+ }
1910
+ return Object.fromEntries(entries);
1911
+ }
1912
+ function mcpServerConfigFromInput(server) {
1913
+ return {
1914
+ id: server.id,
1915
+ ...server.name ? { name: server.name } : {},
1916
+ url: server.url,
1917
+ ...server.allowedTools ? { allowedTools: server.allowedTools } : {},
1918
+ ...server.timeoutMs ? { timeoutMs: server.timeoutMs } : {},
1919
+ cacheToolsList: server.cacheToolsList ?? false
1920
+ };
1921
+ }
1922
+ function mcpServerConfigFromMetadata(server) {
1923
+ return {
1924
+ id: server.id,
1925
+ ...server.name ? { name: server.name } : {},
1926
+ url: server.url,
1927
+ cacheToolsList: false
1928
+ };
1929
+ }
1930
+ function settingsWithSessionMcpServerConfigs(settings, servers) {
1931
+ if (servers.length === 0) {
1932
+ return settings;
1933
+ }
1934
+ const sessionIds = new Set(servers.map((server) => server.id));
1935
+ return {
1936
+ ...settings,
1937
+ mcpServers: [
1938
+ ...settings.mcpServers.filter((server) => !sessionIds.has(server.id)),
1939
+ ...servers
1940
+ ]
1941
+ };
1942
+ }
1943
+ function settingsWithSessionMcpServerMetadata(settings, servers) {
1944
+ return settingsWithSessionMcpServerConfigs(settings, servers.map(mcpServerConfigFromMetadata));
1945
+ }
1946
+ function validateSessionMcpServersForCreate(settings, grant, servers) {
1947
+ if (servers.length === 0) {
1948
+ return { runtimeServers: [], dbServers: [], metadata: [] };
1949
+ }
1950
+ requirePermission(grant, "mcp_servers:attach");
1951
+ const encryptionKey = requireEnvironmentEncryption(settings);
1952
+ const existingIds = new Set(settings.mcpServers.map((server) => server.id));
1953
+ const seenIds = /* @__PURE__ */ new Set();
1954
+ const runtimeServers = [];
1955
+ const dbServers = [];
1956
+ const metadata = [];
1957
+ for (const server of servers) {
1958
+ if (seenIds.has(server.id)) {
1959
+ throw new HTTPException8(422, { message: `duplicate session MCP server id: ${server.id}` });
1960
+ }
1961
+ seenIds.add(server.id);
1962
+ if (reservedSessionMcpServerIds.has(server.id) || existingIds.has(server.id)) {
1963
+ throw new HTTPException8(422, { message: `MCP server id already exists: ${server.id}` });
1964
+ }
1965
+ const headers = normalizedSessionMcpCredentialHeaders(server.headers);
1966
+ const headersEncrypted = Object.fromEntries(
1967
+ Object.entries(headers).map(([name, value]) => [name, encryptEnvironmentValue2(encryptionKey, value)])
1968
+ );
1969
+ runtimeServers.push(mcpServerConfigFromInput(server));
1970
+ dbServers.push({
1971
+ id: server.id,
1972
+ name: server.name ?? null,
1973
+ url: server.url,
1974
+ allowedTools: server.allowedTools ?? null,
1975
+ timeoutMs: server.timeoutMs ?? null,
1976
+ cacheToolsList: server.cacheToolsList ?? false,
1977
+ headersEncrypted
1978
+ });
1979
+ metadata.push({
1980
+ id: server.id,
1981
+ name: server.name ?? null,
1982
+ url: server.url,
1983
+ headerNames: Object.keys(headersEncrypted).sort(),
1984
+ credentialVersion: 1
1985
+ });
1986
+ }
1987
+ return { runtimeServers, dbServers, metadata };
1988
+ }
1989
+ function validateSessionMcpCredentialUpdates(input) {
1990
+ if (input.updates.length === 0) {
1991
+ return [];
1992
+ }
1993
+ requirePermission(input.grant, "mcp_servers:attach");
1994
+ const encryptionKey = requireEnvironmentEncryption(input.settings);
1995
+ const knownIds = new Set(input.session.mcpServers.map((server) => server.id));
1996
+ const seenIds = /* @__PURE__ */ new Set();
1997
+ const encryptedUpdates = input.updates.map((update) => {
1998
+ if (seenIds.has(update.id)) {
1999
+ throw new HTTPException8(422, { message: `duplicate session MCP credential update id: ${update.id}` });
2000
+ }
2001
+ seenIds.add(update.id);
2002
+ if (!knownIds.has(update.id)) {
2003
+ throw new HTTPException8(422, { message: `unknown session MCP server id: ${update.id}` });
2004
+ }
2005
+ const headers = normalizedSessionMcpCredentialHeaders(update.headers);
2006
+ return {
2007
+ id: update.id,
2008
+ headersEncrypted: Object.fromEntries(
2009
+ Object.entries(headers).map(([name, value]) => [name, encryptEnvironmentValue2(encryptionKey, value)])
2010
+ )
2011
+ };
2012
+ });
2013
+ return encryptedUpdates;
2014
+ }
1881
2015
  async function createAndStartSession(input) {
1882
2016
  const sessionMetadata = {
1883
2017
  ...input.metadata,
@@ -1903,7 +2037,8 @@ async function createAndStartSession(input) {
1903
2037
  parentSessionId: input.parentSessionId ?? null,
1904
2038
  createIdempotencyKey: input.createIdempotencyKey,
1905
2039
  sandboxGroupId: input.sandboxGroupId ?? null,
1906
- ...input.sandboxOs ? { sandboxOs: input.sandboxOs } : {}
2040
+ ...input.sandboxOs ? { sandboxOs: input.sandboxOs } : {},
2041
+ mcpServers: input.mcpServers ?? []
1907
2042
  });
1908
2043
  if (!created) {
1909
2044
  return keyed;
@@ -1923,7 +2058,8 @@ async function createAndStartSession(input) {
1923
2058
  firstPartyMcpPermissions: input.firstPartyMcpPermissions ?? null,
1924
2059
  parentSessionId: input.parentSessionId ?? null,
1925
2060
  sandboxGroupId: input.sandboxGroupId ?? null,
1926
- ...input.sandboxOs ? { sandboxOs: input.sandboxOs } : {}
2061
+ ...input.sandboxOs ? { sandboxOs: input.sandboxOs } : {},
2062
+ mcpServers: input.mcpServers ?? []
1927
2063
  });
1928
2064
  return await finishStartSession(input, session);
1929
2065
  }
@@ -1947,7 +2083,8 @@ async function finishStartSession(input, session) {
1947
2083
  type: "session.created",
1948
2084
  payload: {
1949
2085
  status: "queued",
1950
- ...input.environment ? { environmentId: input.environment.id, environmentName: input.environment.name } : {}
2086
+ ...input.environment ? { environmentId: input.environment.id, environmentName: input.environment.name } : {},
2087
+ ...input.sessionMcpServers?.length ? { mcpServers: input.sessionMcpServers } : {}
1951
2088
  }
1952
2089
  },
1953
2090
  ...goal ? [{
@@ -2057,10 +2194,14 @@ async function postUserMessageTurn(input) {
2057
2194
  const requestedModel = input.model ?? null;
2058
2195
  const requestedReasoningEffort = input.reasoningEffort ?? null;
2059
2196
  assertConfiguredModel(settings, requestedModel);
2060
- const appended = await appendSessionEventsWithLockedSessionUpdate(db, workspaceId, sessionId, (lockedSession) => {
2197
+ const appended = await appendSessionEventsWithLockedSessionUpdate(db, workspaceId, sessionId, async (lockedSession, lockedUpdate) => {
2061
2198
  if (lockedSession.status === "cancelled") {
2062
2199
  throw new HTTPException8(409, { message: `session is ${lockedSession.status}; cannot accept a new user message` });
2063
2200
  }
2201
+ const mcpCredentialUpdates = input.mcpCredentialUpdates?.length ? await lockedUpdate.updateSessionMcpServerCredentials(input.mcpCredentialUpdates) : { servers: [], missingIds: [] };
2202
+ if (mcpCredentialUpdates.missingIds.length > 0) {
2203
+ throw new HTTPException8(422, { message: `unknown session MCP server id: ${mcpCredentialUpdates.missingIds[0]}` });
2204
+ }
2064
2205
  const nextResources = mergeResourceRefs(lockedSession.resources, input.resources);
2065
2206
  const nextTools = mergeToolRefs(lockedSession.tools, input.tools);
2066
2207
  const shouldQueueSession = lockedSession.status === "idle" || lockedSession.status === "failed";
@@ -2073,7 +2214,8 @@ async function postUserMessageTurn(input) {
2073
2214
  ...input.resources.length ? { resources: input.resources } : {},
2074
2215
  ...input.tools.length ? { tools: input.tools } : {},
2075
2216
  ...requestedModel ? { model: requestedModel } : {},
2076
- ...requestedReasoningEffort ? { reasoningEffort: requestedReasoningEffort } : {}
2217
+ ...requestedReasoningEffort ? { reasoningEffort: requestedReasoningEffort } : {},
2218
+ ...mcpCredentialUpdates.servers.length ? { mcpCredentialUpdates: mcpCredentialUpdates.servers } : {}
2077
2219
  },
2078
2220
  ...input.clientEventId ? { clientEventId: input.clientEventId } : {}
2079
2221
  },
@@ -2121,10 +2263,12 @@ async function postUserMessageTurn(input) {
2121
2263
  async function createSessionForRequest(deps, grant, workspaceId, rawPayload) {
2122
2264
  const { settings, db, bus, workflowClient, objectStorage } = deps;
2123
2265
  const payload = CreateSessionRequest.parse(rawPayload);
2124
- const runtimeSettings = await settingsWithEnabledCapabilityMcpServers(db, workspaceId, settings);
2266
+ const capabilityRuntimeSettings = await settingsWithEnabledCapabilityMcpServers(db, workspaceId, settings);
2267
+ const sessionMcpServers = validateSessionMcpServersForCreate(capabilityRuntimeSettings, grant, payload.mcpServers);
2268
+ const runtimeSettings = settingsWithSessionMcpServerConfigs(capabilityRuntimeSettings, sessionMcpServers.runtimeServers);
2125
2269
  const resources = normalizeResources(payload.resources);
2126
2270
  const requestedTools = validateToolRefs(payload.tools, runtimeSettings);
2127
- const defaultedTools = hasOwnProperty(rawPayload, "tools") ? requestedTools : withDefaultEnabledCapabilityMcpTools(requestedTools, settings, runtimeSettings);
2271
+ const defaultedTools = hasOwnProperty(rawPayload, "tools") ? requestedTools : withDefaultEnabledCapabilityMcpTools(requestedTools, settings, capabilityRuntimeSettings);
2128
2272
  const tools = withFirstPartyTools(defaultedTools, runtimeSettings);
2129
2273
  await validateGitHubRepositorySelection(db, workspaceId, resources);
2130
2274
  if (resources.some((resource) => resource.kind === "file") && !objectStorage) {
@@ -2228,6 +2372,8 @@ async function createSessionForRequest(deps, grant, workspaceId, rawPayload) {
2228
2372
  environment: environment ? { id: environment.id, name: environment.name } : null,
2229
2373
  goal: payload.goal ?? null,
2230
2374
  firstPartyMcpPermissions,
2375
+ mcpServers: sessionMcpServers.dbServers,
2376
+ sessionMcpServers: sessionMcpServers.metadata,
2231
2377
  parentSessionId,
2232
2378
  createIdempotencyKey: payload.idempotencyKey ?? null,
2233
2379
  // Create-time machine targeting (A-2a): when a target sandbox is named, the
@@ -2251,11 +2397,12 @@ async function createSessionForRequest(deps, grant, workspaceId, rawPayload) {
2251
2397
  }
2252
2398
  async function acceptSessionUserMessage(deps, grant, workspaceId, sessionId, input) {
2253
2399
  const { settings, db, bus, workflowClient, objectStorage } = deps;
2254
- const runtimeSettings = await settingsWithEnabledCapabilityMcpServers(db, workspaceId, settings);
2400
+ const capabilityRuntimeSettings = await settingsWithEnabledCapabilityMcpServers(db, workspaceId, settings);
2401
+ const existingSession = await requireSession2(db, workspaceId, sessionId);
2402
+ const runtimeSettings = settingsWithSessionMcpServerMetadata(capabilityRuntimeSettings, existingSession.mcpServers);
2255
2403
  const requestedResources = normalizeResources(input.resources ?? []);
2256
2404
  const validatedTools = validateToolRefs(input.tools ?? [], runtimeSettings);
2257
- const requestedTools = input.toolsProvided ? validatedTools : withDefaultEnabledCapabilityMcpTools(validatedTools, settings, runtimeSettings);
2258
- const existingSession = await requireSession2(db, workspaceId, sessionId);
2405
+ const requestedTools = input.toolsProvided ? validatedTools : withDefaultEnabledCapabilityMcpTools(validatedTools, settings, capabilityRuntimeSettings);
2259
2406
  await requireLimit(deps, {
2260
2407
  accountId: grant.accountId,
2261
2408
  workspaceId,
@@ -2268,6 +2415,12 @@ async function acceptSessionUserMessage(deps, grant, workspaceId, sessionId, inp
2268
2415
  }
2269
2416
  await validateFileResources(db, workspaceId, requestedResources);
2270
2417
  await validateGitHubRepositorySelection(db, workspaceId, [...existingSession.resources, ...requestedResources]);
2418
+ const mcpCredentialUpdates = validateSessionMcpCredentialUpdates({
2419
+ settings,
2420
+ grant,
2421
+ session: existingSession,
2422
+ updates: input.mcpCredentialUpdates ?? []
2423
+ });
2271
2424
  const { accepted, turn } = await postUserMessageTurn({
2272
2425
  db,
2273
2426
  bus,
@@ -2281,6 +2434,7 @@ async function acceptSessionUserMessage(deps, grant, workspaceId, sessionId, inp
2281
2434
  tools: requestedTools,
2282
2435
  model: input.model ?? null,
2283
2436
  reasoningEffort: input.reasoningEffort ?? null,
2437
+ mcpCredentialUpdates,
2284
2438
  ...input.clientEventId ? { clientEventId: input.clientEventId } : {}
2285
2439
  });
2286
2440
  await recordWorkspaceUsage(deps, {
@@ -2608,6 +2762,7 @@ export {
2608
2762
  scheduledTaskTriggerToken,
2609
2763
  settingsWithEnabledCapabilityMcpServers,
2610
2764
  settingsWithMcpCapabilityServers,
2765
+ settingsWithSessionMcpServerMetadata,
2611
2766
  stableJson,
2612
2767
  swapActiveSandbox,
2613
2768
  syncCreatedScheduledTask,